比较性能:循环与迭代器

要确定是使用循环还是迭代器,您需要知道哪个 实现速度更快:具有显式循环的函数版本或具有迭代器的版本。searchfor

我们通过加载 The Adventures of 的全部内容来运行基准测试 阿瑟·柯南·道尔爵士 (Sir Arthur Conan Doyle) 将夏洛克·福尔摩斯 (Sherlock Holmes) 带入并寻找 字 内容。以下是 version of the loop 和使用迭代器的版本:Stringsearchfor

test bench_search_for  ... bench:  19,620,300 ns/iter (+/- 915,700)
test bench_search_iter ... bench:  19,234,900 ns/iter (+/- 657,200)

迭代器版本稍微快一些!我们不会解释基准测试代码 这里,因为重点不是要证明这两个版本是等效的 但要大致了解这两种实现的比较 性能方面。

为了获得更全面的基准测试,您应该使用 大小多样、字数不同、字长不同 作为 , 以及各种其他变体。关键是: 迭代器虽然是一个高级抽象,但可以编译为大致的 与您自己编写较低级别代码的代码相同。迭代器是一个 Rust 的零成本抽象,我们的意思是使用抽象 不会产生额外的运行时开销。这类似于 Bjarne 的方式 C++ 的原始设计者和实现者 Stroustrup 在“Foundations of C++”(2012 年)中定义了零开销contentsquery

通常,C++ 实现遵循零开销原则:您 不使用,您不需要付费。更进一步:你用的,你不能拿 编码更好。

作为另一个示例,以下代码取自音频解码器。这 解码算法使用线性预测数学运算来 根据先前样本的线性函数估计未来值。这 代码使用迭代器链对 scope 中的三个变量进行一些数学运算:数据切片、12 数组和数量 以移动 中的数据。我们已经在这个例子中声明了变量 但没有给他们任何价值;虽然这段代码没有太大的意义 在其上下文之外,它仍然是一个简洁的真实示例,说明了 Rust 将高级想法转换为低级代码。buffercoefficientsqlp_shift

let buffer: &mut [i32];
let coefficients: [i64; 12];
let qlp_shift: i16;

for i in 12..buffer.len() {
    let prediction = coefficients.iter()
                                 .zip(&buffer[i - 12..i])
                                 .map(|(&c, &s)| c * s as i64)
                                 .sum::<i64>() >> qlp_shift;
    let delta = buffer[i];
    buffer[i] = prediction as i32 + delta;
}

为了计算 的值,此代码遍历每个 12 个值,并使用该方法对系数进行配对 值中具有前 12 个值。然后,对于每一对,我们 将值相乘,对所有结果求和,然后移位 求和位。predictioncoefficientszipbufferqlp_shift

音频解码器等应用程序中的计算通常优先考虑性能 最高。在这里,我们将使用两个适配器创建一个迭代器,然后 消耗价值。这个 Rust 代码会编译成什么汇编代码?井 在撰写本文时,它将编译为您将手动编写的同一程序集。 根本没有与中值的迭代对应的循环:Rust 知道有 12 次迭代,因此它“展开”了 圈。展开是一种消除循环开销的优化 控制代码,而是为每次迭代生成重复代码 循环。coefficients

所有系数都存储在 registers 中,这意味着访问 values 非常快。运行时对数组访问没有边界检查。 Rust 能够应用的所有这些优化使结果代码 极其高效。现在你知道了这一点,你可以使用迭代器和闭包 无所畏惧!它们使代码看起来更高级别,但不会强加 这样做会带来运行时性能损失。

总结

闭包和迭代器是受函数式编程启发的 Rust 特性 语言理念。它们有助于 Rust 清楚地表达 低级性能中的高级想法。闭包和 迭代器不会影响运行时性能。这是 Rust 的目标是努力提供零成本的抽象。

现在我们已经改进了 I/O 项目的表现力,让我们看看 它的其他一些功能将帮助我们与 世界。cargo

本文档由官方文档翻译而来,如有差异请以官方英文文档(https://doc.rust-lang.org/)为准