
教程文档【免费下载链接】bookThe Rust Programming Language项目地址https://gitcode.com/gh_mirrors/bo/book点击查看免费下载导读本文基于 The Rust Programming Language 官方仓库第 13 章《函数式语言特性》的最后一节聚焦一个几乎所有 Rust 开发者都会纠结的问题使用迭代器iterator编写的代码和手写for循环相比性能到底会不会变差文章以仓库中minigrep项目的search函数为研究对象给出两组实测基准数据并从编译优化循环展开、边界检查消除与零成本抽象zero-cost abstraction的角度解释为什么迭代器并不慢。读完本文你将理解迭代器被编译到接近手写汇编的原理掌握循环与迭代器两种风格的取舍依据并学会在 I/O 密集型程序中放心使用高阶抽象。一、问题缘起search函数的两种实现要判断用循环还是用迭代器最直接的办法是回答一个性能问题search函数用显式for循环的版本与用迭代器的版本哪个更快在 ch13-03-improving-our-io-project.md 中我们先用迭代器重构了minigrep的search函数。两种实现都存在于仓库的 listings 目录中可以逐行对照使用显式for循环的版本listings/ch12-an-io-project/listing-12-19/src/lib.rspub fn searcha(query: str, contents: a str) - Veca str { let mut results Vec::new(); for line in contents.lines() { if line.contains(query) { results.push(line); } } results }使用迭代器适配器adapter的版本listings/ch13-functional-features/listing-13-22/src/lib.rspub fn searcha(query: str, contents: a str) - Veca str { contents .lines() .filter(|line| line.contains(query)) .collect() }直观上很多开发者会本能地认为更底层的for循环应该更快。迭代器版本链式调用lines()、filter和collect看起来像是一层层函数调用的叠加这种直觉是否成立原文档给出的基准测试数据给出了答案。二、基准测试加载《福尔摩斯探案集》全文搜索 the原文档描述了一组真实的基准测试流程将阿瑟·柯南·道尔Sir Arthur Conan Doyle的《福尔摩斯探案集》The Adventures of Sherlock Holmes全文读入一个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)两组数字的解读要点bench_search_forfor循环实现平均每次迭代耗时约19.62 毫秒bench_search_iter迭代器实现平均每次迭代耗时约19.23 毫秒两者的均值差异不足 2%且误差范围/-相互重叠可以认为两种实现的性能基本相当。原文档特别说明这里并不打算证明两种版本完全等价基准测试的目的也不是给出谁更快的精确结论而是建立一个大致的性能观感。若要做更全面的评测建议变化contents的文本内容与规模、更换不同的query单词及单词长度并尝试其它各种变体以覆盖更多真实场景。需要说明的是基准测试代码本身并未在仓库 listings 中提供原文档明确说明不会展开讲解 benchmark 代码但minigrep的两种search实现、配套的单元测试与示例文本poem.txt均可在 listings 目录中直接查看与运行循环版实现与测试listings/ch12-an-io-project/listing-12-19/src/lib.rs迭代器版实现与测试listings/ch13-functional-features/listing-13-22/src/lib.rs示例搜索文本可直接作为contents输入listings/ch13-functional-features/listing-13-22/poem.txt三、为什么结果如此接近零成本抽象Zero-Cost Abstraction基准测试的结果看似反直觉但背后的原理正是原文档的核心论点迭代器虽然是一种高层抽象但会被编译成与你亲手编写底层代码时几乎相同的机器代码。迭代器是 Rust 的零成本抽象之一——使用这种抽象不会带来额外的运行时开销。所谓零成本抽象原文档借用了 C 之父 Bjarne Stroustrup 在 2012 年 ETAPS 主题演讲《Foundations of C》中的定义一般而言C 实现遵循零开销原则你用不到的东西你不为之付费。更进一步你用到的东西你自己手写也不可能写得更好。这段话被原文档原样引用以阐明 Rust 与 C 在抽象哲学上的一致性。Rust 吸收了这一原则让程序员用接近高层语言的表达方式写出在性能上接近手写汇编的代码。3.1 编译器的优化手段原文档进一步指出在许多情况下使用迭代器的 Rust 代码会编译成与你手写时相同的汇编指令。以下几类优化对最终代码效率起关键作用循环展开loop unrolling编译器将多次迭代的循环体复制展开减少循环控制分支与跳转的开销提升指令级并行度边界检查消除bounds-check elimination对数组/切片访问时编译器若能静态证明索引不会越界就会剔除运行时边界检查消除这部分分支开销。迭代器版本之所以能获得与手写循环相同的优化效果是因为filter、collect等迭代器方法大多是内联inlined的LLVM 在编译期看到的是与显式循环几乎一致的线性代码流自然可以套用同样的优化管线。3.2 迭代器方法的源码佐证从仓库 src/ch13-02-iterators.md 可以看到迭代器设计的底层结构——标准库中的Iteratortrait 定义如下pub trait Iterator { type Item; fn next(mut self) - OptionSelf::Item; // methods with default implementations elided }关键在于实现者只需实现next一个方法。next每次返回一个元素包装在Some中迭代完毕时返回None其余大量方法如filter、map、sum、collect都由标准库基于next提供默认实现迭代器是惰性lazy的创建迭代器本身不产生任何计算只有调用消费型方法consuming adapters如collect、sum触发next调用时元素才会被逐个处理。这种按需拉取的模型让编译器有机会把整条链式调用扁平化、内联为一段紧凑的循环代码因为每个元素的处理逻辑被封装为对next的调用、过滤逻辑封装为闭包闭包同样会被内联最终生成的代码与手写for循环几乎没有结构差异。这也是用迭代器和闭包无需担忧性能的技术根源它们让代码看起来层次更高但运行时并不因此付出性能代价。四、在 minigrep 实战中的进一步收益惰性迭代原文档所属的第 13 章在 ch13-03-improving-our-io-project.md 中还给出了一项与性能间接相关的实战改进把search的返回值从Veca str改为impl IteratorItem a str去掉中间的collect调用让search本身成为一个迭代器适配器。改造之后run函数中的for line in results循环可以直接利用迭代器的惰性每找到一个匹配行就立即打印输出而改造之前程序必须等collect收集完所有结果后才能开始打印在搜索超大文件时这种差异会带来可感知的行为变化——这也是原文档建议改动前后分别用 minigrep 搜索一个大文件、观察行为差异的原因。// 返回迭代器的 search示意改造自 listing-13-22 pub fn searcha(query: str, contents: a str) - impl IteratorItem a str { contents .lines() .filter(|line| line.contains(query)) }注意改造返回值类型后search的单元测试也需要同步更新原测试断言的是Veca str结果。这类延迟计算 即时消费的特性是从collect全量收集到流式处理之间的一种免费性能优化——它同样建立在前文所述的惰性迭代机制之上。五、实践建议何时选择哪种风格那么在自己的代码里究竟该用循环还是迭代器原文档的观点很明确大多数 Rust 程序员倾向于迭代器风格。它上手时稍微难一点但一旦熟悉各种迭代器适配器的用途迭代器往往更容易理解。理由是迭代器版本省去了摆弄循环索引、手动构建新向量的样板代码代码直接聚焦于高层目标——比如filter闭包中的过滤条件读者一眼就能看到每个元素需要满足什么条件。这是 ch13-03-improving-our-io-project.md 中重构search时同样的考量用filtercollect取代可变results向量减少了可变中间状态也让未来把搜索并行化无需再管理对共享results向量的并发访问成为可能。但两种实现是否真正等价仍是需要回答的问题——本文第一节的基准数据已经给出答案性能上两者基本等价因此你完全可以根据可读性而不是性能来做选择。六、总结原文档为本章src/ch13-04-performance.md画上句号时总结道闭包closures与迭代器iterators是 Rust 受函数式编程语言思想启发的特性它们共同支撑起 Rust以底层性能表达高层思想的能力闭包与迭代器的实现方式保证了运行时性能不受影响——这是 Rust 追求零成本抽象目标的一部分在《福尔摩斯探案集》全文检索the的基准中for循环版与迭代器版的耗时分别为约 19.62ms 与 19.23ms两者误差区间重叠、性能基本一致直观验证了迭代器无额外运行时开销这一论断。理解了这一点你就可以放心地在日常代码中使用闭包与迭代器它们让代码显得更高层却不会因此牺牲运行时性能——这正是零成本抽象赋予 Rust 开发者的自由。延伸阅读仓库内本章完整脉络src/ch13-00-functional-features.md闭包与捕获环境src/ch13-01-closures.md迭代器 trait、惰性与适配器src/ch13-02-iterators.mdminigrep 迭代器重构全流程src/ch13-03-improving-our-io-project.md迭代器版search的完整实现与测试listings/ch13-functional-features/listing-13-22/src/lib.rsConfig::build用迭代器改写含impl IteratorItem String签名listings/ch13-functional-features/listing-13-20/src/main.rs重构前的Config::build含clone调用对比listings/ch13-functional-features/listing-12-23-reproduced/src/main.rs赞分享教程文档【免费下载链接】bookThe Rust Programming Language项目地址https://gitcode.com/gh_mirrors/bo/book点击查看免费下载相关推荐Rust 迭代器与循环的性能对比深入解析零成本抽象Loops vs. IteratorsRust 迭代器与循环的性能对比深入解析零成本抽象Loops vs. Iterators 本篇技术指南聚焦 Rust 中一个常被追问的核心问题使用迭代器教程文档Comprehensive Rust 课程精讲手写 Iterator trait理解 Rust 迭代器的惰性与零开销抽象Comprehensive Rust 课程精讲手写 Iterator trait理解 Rust 迭代器的惰性与零开销抽象 本篇技术指南以 Comprehen文档教程《The Rust Programming Language》第 13 章精讲用闭包与迭代器写出零开销、函数式风格的惯用 Rust《The Rust Programming Language》第 13 章精讲用闭包与迭代器写出零开销、函数式风格的惯用 Rust 本文基于《The Rust教程文档上一篇xLua 官方教程深度解读Lua 脚本加载机制与 C/Lua 双向互调实战下一篇ruflo /create-plugin 命令实战指南交互式脚手架一个符合契约的 Claude Code 插件创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考