Updated 'src/functional_features/performance.md'.

This commit is contained in:
Hector PENG
2026-03-31 15:33:47 +08:00
parent 06956eb4ed
commit 5c208ad5ec
3 changed files with 31 additions and 17 deletions

View File

@@ -2,7 +2,7 @@ pub fn search<'a> (query: &str, contents: &'a str) -> Vec<&'a str> {
contents
.lines()
.filter(|line| line.contains(query))
.collect
.collect()
}
pub fn search_case_insensitive<'a>(

View File

@@ -92,7 +92,7 @@
- [闭包](functional_features/closures.md)
- [以迭代器处理一序列项目](functional_features/iterators.md)
- [改进咱们的 I/O 项目](functional_features/improving_io_project.md)
- [性能比较:循环与迭代器](functional_features/performance.md)
- [循环与迭代器下的性能问题](functional_features/performance.md)
- [Cargo 的其他方面及 Crates.io](Ch14_More_about_Cargo_and_Crates-io.md)
- [使用发布配置文件自定义构建](crates-io/release_profiles.md)

View File

@@ -1,24 +1,33 @@
# 性能比较:循环与迭代器
# 循环与迭代器下的性能问题
**Comparing Performance: Loops vs. Iterators**
要确定是使用循环还是迭代器,咱们需要知道哪种实现更快:带有显式 `for` 循环的 `search` 函数版本,还带有迭代器的版本。
为确定是使用循环还是迭代器,咱们需要知道哪种实现更快:带有显式 `for` 循环的 `search` 函数版本,还带有迭代器的版本。
我们通过将阿瑟-柯南-道尔爵士的《福尔摩斯历险记》的全部内容加载到一个字符串中,并在内容中寻找单词 "福尔摩斯",进行了一次基准测试。下面是使用 `for` 循环的 `search` 版本和使用迭代器的版本的基准测试结果:
我们通过加载 Arthur Conan Doyle 爵士所著的《福尔摩斯历险记》的全部内容到一个 `String` 中,然后在内容中查找单词 *the*,运行了一个基准测试。下面是该基准测试对使用 `for` 循环的 `search` 版本,和使用迭代器的版本的结果:
```console
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 的 *零成本抽象zero-cost abstractions* 之一,我们的意思是使用该抽象不会带来额外的运行时开销。这类似于 C++ 最初设计者和实现者 Bjarne Stroustrup 在《C++ 基础》2012中对零开销的定义:
出于更全面的测试,咱们应该使用不同大小的各种文本作为 `contents`、不同长度的不同单词作为 `query`,以及所有类别的变量来检查。重点是:迭代器虽然属于高级抽象,仍会被编译成与咱们自己编写的低级(地层)代码大致相同的代码。迭代器属于 Rust 的 *零成本抽象* 之一,这意味着使用这一抽象不会带来额外的运行时开销。这类似于 C++ 最初设计者和实现者 Bjarne Stroustrup 在其 2012 年 ETAPS 的主题演讲 [《C++ 基础》](https://www.stroustrup.com/ETAPS-corrected-draft.pdf) 中,对 “零开销的定义:
> 一般来说C++ 的实现遵循零开销原则:咱们不使用的东西,咱们不需要付出开销。进一步:咱们使用的东西,咱们不可能手写出更良好的代码the zero-overhead principle: What you don't use, you don't pay for. And further: What you do use, you couldn't hand code any better。
> 一般来说C++ 的实现遵循零开销原则:不使用的东西,不需要付出开销。进一步:咱们真正用到的东西,咱们无法更好地手工编写代码the zero-overhead principle: What you don't use, you don't pay for. And further: What you do use, you couldn't hand code any better。
作为另一个示例以下代码取自某个音频解码器。解码算法使用线性预测的数学运算根据之前样本的线性函数来估计后面的数值。此代码使用迭代器链an iterator chain对作用域中三个变量执行一些数学计算由一些数据构成 `buffer` 切片,由 12 个 `coeffecients` 构成的一个数组,以及保存着数据偏移量的 `qlp_shift`。咱们已在这个示例中声明了变量,但并未给他们任何值;尽管此代码在其上下文之外没有什么意义,但他仍不失为说明 Rust 如何将高级别的概念,转化为低级别代码的一个简练、真实的示例。
> **注意**
>
> - zero-cost abstraction, 零成本抽象
作为另一个示例,以下代码取自某个音频解码器。该解码算法使用线性预测数学运算,根据前几个采样值的线性函数来估计未来值。这段代码使用迭代器链,对作用域中的三个变量执行一些数学计算:
- 一个数据的 `buffer` 切片
- 一个包含 12 个 `coefficients` 的数组
- 以及 `qlp_shift` 中偏移数据的数量
我们在这个示例中声明了这些变量,但未赋予他们任何值;尽管这段代码在其上下文之外没有太大意义,但对于 Rust 如何将高级别的概念转换成低级别代码,他仍是个简洁且贴近实际的示例。
```rust
let buffer: &mut [i32];
@@ -35,19 +44,24 @@ for i in 12..buffer.len() {
}
```
为了计算预测值,此代码遍历 `coefficients` 中 12 个值中的每一个,并使用 `zip` 方法将系数值与 `buffer` 中的前 12 个值配对。随后对每个数值对,咱们将值相乘,对所有结果求和,并将总和中的二进制位向右`qlp_shift` 位。
为了计算 `prediction` 的值,这段代码遍历 `coefficients` 中 12 个值中的每一个,并使用 `zip` 方法将系数值与 `buffer` 中的前 12 个值配对。然后,针对每个数值对,咱们将两个值相乘,对所有结果求和,并将总和中的二进制位向右移 `qlp_shift` 位。
像是音频解码器这样的应用中的计算,通常最优先考虑的是性能。在这里,我们正在创建个迭代器,使用两个适配器,然后消费这个值。这段 Rust 代码会编译成什么汇编代码呢?好吧,在写这篇文章时,他可以编译成与咱们用手写的相同汇编代码。在系数的迭代过程中完全没有对应的循环: Rust 知道有 12 迭代,所以他 “展开” 了这个循环。所谓 “展开unrolling”消除循环控制代码方面的开销,而代之以生成循环历次迭代的重复代码的一种优化,*unrolling* is an optimization that removes the overhead of the loop controlling code and instead generates repetitive code for each iteration of the loop
音频解码器这样的应用中的计算,通常会将性能放在第一位。在这里,我们创建个迭代器,使用两个适配器,然后消费值。这段 Rust 代码会编译成什么样的汇编代码呢?好吧,在撰写本文时,他编译成与咱们用手工编写的相同汇编代码。根本不存在与对 `coefficients` 中的值的遍历的对应的循环Rust 知道有 12 迭代,因此他会 “展开” 循环。所谓 *展开*,属于一种优化,消除循环控制代码的开销,而生成循环历次迭代的重复代码。
所有系数都被存储在寄存器中,这意味着访问这些值的速度非常快。在运行时对数组访问没有边界检查。Rust 能够应用的所有这些优化使得所产生的代码非常高效。现在咱们知道了这些,咱们就可以毫无顾忌地使用迭代器和闭包!他们使代码看起来更高级,不会因为这样做带来运行时的性能损失。
所有系数都被存储在寄存器中,这意味着访问这些值极为快速。运行时对数组访问没有边界检查。Rust 能够应用的所有这些优化,使生成的代码极其高效。现在咱们知道了这点,就可以放心地使用迭代器和闭包!他们使代码看起来更高级,不会因为这样做带来运行时的性能损失。
> **译注**:上面的音频解码器示例,已从新版本中移除。
在许多情况下,使用迭代器的 Rust 代码,都会编译成与咱们手动编写的同样汇编代码。诸如循环展开及消除对数组访问的边界检查等优化,会得以应用而使生成的代码极其高效。现在咱们知道了这点,就可以放心地使用迭代器和闭包了!他们让代码看起来更高级,却不会因为这样做而带来运行时的性能损失。
# 本章小结
闭包与迭代器,是 Rust 受函数式编程概念启发的两项特性。他们有助于 Rust 以底层性能清楚表达高级别概念的能力。闭包与迭代器的实现,不会影响到运行时性能。这正是 Rust 致力于提供零成本抽象目标的一部分。
闭包与迭代器属于 Rust 受函数式编程思想启发的两项特性。他们贡献了 Rust 以底层性能,清晰地表达高级别概念的能力。闭包与迭代器的实现方式,确保了运行时性能不受影响。这属于 Rust 致力于提供零成本抽象目标的一部分。
现在我们已经改进了 I/O 项目的表达能力,我们来看看 `cargo` 的更多特性,这些特性将帮助我们与世界分享这个项目。
现在我们已经改进了 I/O 项目的表达能力,我们来看看 `cargo` 的更多特性,这些特性将帮助我们与世界分享项目。
End