为什么 Rust 文档说数组上的 while 循环比 for 循环慢?

Chu*_*han 2 rust

在阅读 Rust 文档时,我偶然发现了使用 while 循环(带有索引)迭代数组的代码a

fn main() {
    let a = [10, 20, 30, 40, 50];
    let mut index = 0;

    while index < 5 {
        println!("the value is: {}", a[index]);

        index += 1;
    }
}
Run Code Online (Sandbox Code Playgroud)

文档说:

... 这种方法容易出错;如果索引长度不正确,我们可能会导致程序崩溃。它也很慢,因为编译器添加了运行时代码以在循环的每次迭代中对每个元素执行条件检查。

第一个原因不言自明。第二个原因是我感到困惑的地方。

此外,他们建议为此使用 for 循环。

fn main() {
    let a = [10, 20, 30, 40, 50];

    for element in a.iter() {
        println!("the value is: {}", element);
    }
}
Run Code Online (Sandbox Code Playgroud)

我似乎无法理解这一点。Rust 编译器是否有某种行为?

She*_*ter 5

两部分是互补的:

如果索引长度不正确,我们可能会导致程序崩溃。

每次编写时some_slice[some_index],标准库都会执行以下操作:

if some_index < some_slice.len() {
    some_slice.get_the_value_without_checks(some_index)
} else {
    panic!("Hey, stop that");
}
Run Code Online (Sandbox Code Playgroud)

编译器添加运行时代码以对每个元素执行条件检查

在一个循环中,结果如下:

while some_index < limit {
    if some_index < some_slice.len() {
        some_slice.get_the_value_without_checks(some_index)
    } else {
        panic!("Hey, stop that");
    }
    some_index += 1;
}
Run Code Online (Sandbox Code Playgroud)

那些重复的条件不是最有效的代码。

Iteratorfor slice的实现利用unsafe代码来提高效率,但代价是代码更复杂。迭代器包含指向数据的原始指针,但确保您永远不会滥用它们导致内存不安全。无需在每一步都执行该条件,迭代器解决方案通常更快1。它或多或少相当于:

while some_index < limit {
    some_slice.get_the_value_without_checks(some_index)
    some_index += 1;
}
Run Code Online (Sandbox Code Playgroud)

也可以看看:


1 — 正如Matthieu M. 指出的

应该注意的是,优化器可能(也可能不)能够在这种while情况下删除边界检查。如果成功,那么性能是等价的;如果失败,您的代码会突然变慢。在微基准测试中,使用简单的代码,更改是否会成功……但这可能不会带到您的生产代码中,或者现在可能会进行,并且循环体中的下一次更改将阻止优化等……总之,while循环可以是性能定时炸弹。

也可以看看:

  • 应该注意的是,优化器可能(也可能不)能够在“while”的情况下移除边界检查。如果成功,那么性能是等价的;如果失败,您的代码会突然变慢。在微基准测试中,使用简单的代码,更改它会成功......但这可能不会带到您的生产代码中,或者现在可能会进行,并且循环体中的下一次更改将阻止优化等......总之,一个“while”循环可能是一个性能定时炸弹。 (2认同)