Function std::hint::black_box

1.66.0 (const: unstable) · source ·
pub fn black_box<T>(dummy: T) -> T
Expand description

一个标识函数,hints 编译器对 black_box 能做的事情保持最大限度的悲观。

std::convert::identity 不同,鼓励 Rust 编译器假定 black_box 可以以允许 Rust 代码使用的任何可能有效方式使用 dummy,而不会在调用代码中引入未定义的行为。

此属性使 black_box 可用于编写不需要进行某些优化 (例如基准测试) 的代码。

但是请注意,black_box 仅 (并且只能) 以 “best-effort” 为基础提供。它可以阻止优化的程度可能会有所不同,具体取决于所使用的平台和代码源后端。 程序不能依赖 black_box正确性,除了它作为身份函数。 因此,**不得依赖它来控制关键程序行为。**这立即排除了将此函数直接用于加密或安全目的的任何可能性。

这什么时候有用?

虽然不适合那些关键任务的情况,但通常可以依赖 black_box 的功能进行基准测试,并且应该在那里使用。 它将尝试确保编译器不会根据上下文优化部分预期的测试代码。 例如:

fn contains(haystack: &[&str], needle: &str) -> bool {
    haystack.iter().any(|x| x == &needle)
}

pub fn benchmark() {
    let haystack = vec!["abc", "def", "ghi", "jkl", "mno"];
    let needle = "ghi";
    for _ in 0..10 {
        contains(&haystack, needle);
    }
}
Run

编译器理论上可以进行如下优化:

  • needlehaystack 一直一样,把调用移到循环外的 contains,删除循环
  • Inline contains
  • needlehaystack 的值在编译时已知,contains 始终为真。去掉调用,换成 true
  • contains 的结果没有做任何事情: 完全删除这个函数调用
  • benchmark 现在没有任何意义: 删除这个函数

上述所有情况不太可能发生,但编译器肯定能够进行一些优化,这可能会导致基准测试非常不准确。这就是 black_box 的用武之地:

use std::hint::black_box;

// 相同 `contains` 函数
fn contains(haystack: &[&str], needle: &str) -> bool {
    haystack.iter().any(|x| x == &needle)
}

pub fn benchmark() {
    let haystack = vec!["abc", "def", "ghi", "jkl", "mno"];
    let needle = "ghi";
    for _ in 0..10 {
        // 调整我们的基准循环内容
        black_box(contains(black_box(&haystack), black_box(needle)));
    }
}
Run

这实质上告诉编译器阻止对 black_box 的任何调用的优化。 所以,它现在:

  • 将参数到 contains 都视为不可预测: contains 的主体不能再根据参数值进行优化
  • 将调用 contains 及其结果视为易变的: benchmark 的主体无法优化它

这使我们的基准测试更现实地了解函数如何在原位使用,其中函数通常在编译时未知,结果以某种方式使用。