比较性能:循环与迭代器
要确定是使用循环还是迭代器,您需要知道哪个
实现速度更快:具有显式循环的函数版本或具有迭代器的版本。search
for
我们通过加载 The Adventures of 的全部内容来运行基准测试
阿瑟·柯南·道尔爵士 (Sir Arthur Conan Doyle) 将夏洛克·福尔摩斯 (Sherlock Holmes) 带入并寻找
字 在内容。以下是
version of the loop 和使用迭代器的版本: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)
迭代器版本稍微快一些!我们不会解释基准测试代码 这里,因为重点不是要证明这两个版本是等效的 但要大致了解这两种实现的比较 性能方面。
为了获得更全面的基准测试,您应该使用
大小多样、字数不同、字长不同
作为 , 以及各种其他变体。关键是:
迭代器虽然是一个高级抽象,但可以编译为大致的
与您自己编写较低级别代码的代码相同。迭代器是一个
Rust 的零成本抽象,我们的意思是使用抽象
不会产生额外的运行时开销。这类似于 Bjarne 的方式
C++ 的原始设计者和实现者 Stroustrup 在“Foundations of C++”(2012 年)中定义了零开销:contents
query
通常,C++ 实现遵循零开销原则:您 不使用,您不需要付费。更进一步:你用的,你不能拿 编码更好。
作为另一个示例,以下代码取自音频解码器。这
解码算法使用线性预测数学运算来
根据先前样本的线性函数估计未来值。这
代码使用迭代器链对 scope 中的三个变量进行一些数学运算:数据切片、12 数组和数量
以移动 中的数据。我们已经在这个例子中声明了变量
但没有给他们任何价值;虽然这段代码没有太大的意义
在其上下文之外,它仍然是一个简洁的真实示例,说明了 Rust
将高级想法转换为低级代码。buffer
coefficients
qlp_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 个值。然后,对于每一对,我们
将值相乘,对所有结果求和,然后移位
求和位。prediction
coefficients
zip
buffer
qlp_shift
音频解码器等应用程序中的计算通常优先考虑性能
最高。在这里,我们将使用两个适配器创建一个迭代器,然后
消耗价值。这个 Rust 代码会编译成什么汇编代码?井
在撰写本文时,它将编译为您将手动编写的同一程序集。
根本没有与中值的迭代对应的循环:Rust 知道有 12 次迭代,因此它“展开”了
圈。展开是一种消除循环开销的优化
控制代码,而是为每次迭代生成重复代码
循环。coefficients
所有系数都存储在 registers 中,这意味着访问 values 非常快。运行时对数组访问没有边界检查。 Rust 能够应用的所有这些优化使结果代码 极其高效。现在你知道了这一点,你可以使用迭代器和闭包 无所畏惧!它们使代码看起来更高级别,但不会强加 这样做会带来运行时性能损失。
总结
闭包和迭代器是受函数式编程启发的 Rust 特性 语言理念。它们有助于 Rust 清楚地表达 低级性能中的高级想法。闭包和 迭代器不会影响运行时性能。这是 Rust 的目标是努力提供零成本的抽象。
现在我们已经改进了 I/O 项目的表现力,让我们看看
它的其他一些功能将帮助我们与
世界。cargo
本文档由官方文档翻译而来,如有差异请以官方英文文档(https://doc.rust-lang.org/)为准