【Rust自学】13.10. 性能对比:循环 vs. 迭代器 📅 2026/7/23 18:58:09 13.10 性能对比循环 vs. 迭代器13.10.0. 写在正文之前Rust语言在设计过程中受到了很多语言的启发而函数式编程对Rust产生了非常显著的影响。函数式编程通常包括通过将函数作为值传递给参数、从其他函数返回它们、将它们分配给变量以供以后执行等等。在本章中我们会讨论 Rust 的一些特性这些特性与许多语言中通常称为函数式的特性相似- 闭包- 迭代器- 使用闭包和迭代器改进I/O项目-闭包和迭代器的性能本文13.10.1. 一个测试为了运行基准测试将阿瑟·柯南·道尔爵士所著的《夏洛克·福尔摩斯历险记》的全部内容加载到一个String中并在内容中搜索单词the。以下是使用for循环的search版本和使用迭代器的版本的基准测试结果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中的一种高层次抽象它在编译后生成的代码几乎和我们手写的底层代码一样。这叫做零开销抽象Zero-Cost Abstraction。13.10.2. 零开销抽象Zero-Cost Abstraction零开销抽象意味着使用抽象不会引入额外的运行时开销。Rust的迭代器能实现零开销抽象是因为1. 泛型与单态化Rust的迭代器大量使用泛型来定义操作比如Iteratortrait。编译器在编译期间会对每个具体的类型实例化泛型代码生成专门针对这些类型的高效机器代码这个过程叫单态化monomorphization。静态分发编译器根据具体的类型生成直接调用函数的代码不需要在运行时查找函数地址这与通过虚表进行的动态分发不同。优化机会由于类型在编译期是已知的编译器可以对代码进行深入优化比如消除函数调用的开销以及内联。2. 内联与LLVM优化Rust使用LLVM作为其后端编译器。编译器可以通过以下方式优化迭代器链函数内联Rust编译器会内联迭代器中的操作如map、filter等将这些方法展开为一段紧凑的代码而不会有函数调用开销。循环展开与合并多个迭代器方法的调用如map().filter().collect()在编译时会被合并为单个循环。冗余消除例如对于一些多余的中间变量或操作编译器会直接去掉。结果是迭代器链的最终执行代码效率几乎与手写的循环相当。3. 惰性求值Rust的迭代器是惰性的这意味着在调用终结方法如collect()或for_each()之前迭代器不会执行任何实际操作。每个中间操作如map和filter仅创建一个新迭代器并不会立即应用操作。这种惰性设计允许编译器在最终使用迭代器时直接生成针对具体场景优化的代码而不会引入不必要的中间数据结构或计算。4. 无运行时开销Rust的设计原则之一是避免运行时成本。迭代器的实现避免了动态分配和运行时多态Rust迭代器是基于静态类型的通常无需堆分配除非显式使用Box或dyn Iterator。Iteratortrait使用静态分发避免了动态分发。即使需要动态分发也必须显式声明为dyn Iterator。5. 没有额外的抽象成本Rust迭代器通过直接对底层数据结构的操作提供功能而不会引入额外的抽象层。比如调用.iter()生成的迭代器直接操作底层切片或集合开销极低。中间迭代器如Map、Filter在编译时会被优化成一段紧凑的指令而不会引入多余的封装。13.10.3. 一个例子音频解码程序以下代码取自一个音频解码器。解码算法使用线性预测数学运算根据先前样本的线性函数来估计未来值。这段代码使用迭代器链对三个变量执行若干数学运算数据的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值这段代码会遍历coefficients中的12个值并使用zip方法将每个系数与buffer中的前12个值配对。然后对每一对数值相乘对所有结果求和再将总和向右移动qlp_shift位。所有系数都存储在寄存器中这意味着访问这些值非常快。运行时对数组访问没有边界检查。Rust能够应用的所有这些优化使生成的代码极其高效。现在你知道了这一点就可以毫无顾虑地使用迭代器和闭包了它们使代码看起来更高级同时又不会造成运行时性能损失。