比较性能:循环与迭代器

要确定是使用循环还是迭代器,您需要知道哪个 实现速度更快:的search函数中带有显式的for循环或带有迭代器的 version 进行访问。

我们通过加载 The Adventures of 的全部内容来运行基准测试 阿瑟·柯南·道尔爵士 (Sir Arthur Conan Doyle) 将夏洛克·福尔摩斯 (Sherlock Holmes) 改编成String并查找 字 内容。以下是 的版本search使用for循环和版本使用迭代器:

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

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

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

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

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

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;
}

要计算prediction中,此代码遍历每个 12 个值coefficients并使用zip配对系数的方法 值,其中前 12 个值在buffer.然后,对于每一对,我们 将值相乘,对所有结果求和,然后移位 和qlp_shift位。

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

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

总结

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

现在我们已经改进了 I/O 项目的表现力,让我们看看 的更多功能cargo这将帮助我们与 世界。

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