比较性能:循环与迭代器
要确定是使用循环还是迭代器,您需要知道哪个
实现速度更快:的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/)为准