# 循环与迭代器下的性能问题

要确定是使用循环还是迭代器，咱们需要知道哪种实现更快：带有显式 `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 的 *零成本抽象* 之一，这意味着使用这一抽象不会带来额外的运行时开销。这类似于 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。

> **注意**：
>
> - zero-cost abstraction, 零成本抽象



作为另一个示例，以下代码取自某个音频解码器。该解码算法使用线性预测数学运算，根据前几个采样值的线性函数来估计未来值。这段代码使用迭代器链，对作用域中的三个变量执行一些数学计算：

- 一个数据的 `buffer` 切片
- 一个包含 12 个 `coefficients` 的数组
- 以及 `qlp_shift` 中偏移数据的数量

我们在这个示例中声明了这些变量，但未赋予他们任何值；尽管这段代码在其上下文之外没有太大意义，但对于 Rust 如何将高级别的概念转换成低级别代码，他仍是个简洁且贴近实际的示例。

```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 代码会编译成什么样的汇编代码呢？好吧，在撰写本文时，他会编译成与咱们用手工编写的相同汇编代码。根本不存在与对 `coefficients` 中的值的遍历的对应的循环：Rust 知道有 12 次迭代，因此他会 “展开” 循环。所谓 *展开*，属于一种优化，消除循环控制代码的开销，转而生成循环的历次迭代的重复代码。

所有系数都会被存储在寄存器中，这意味着访问这些值极为快速。运行时对数组访问没有边界检查。Rust 能够应用的所有这些优化，使生成的代码极其高效。现在咱们知道了这点，就可以放心地使用迭代器和闭包！他们使代码看起来更高级，却不会因为这样做带来运行时的性能损失。


> **译注**：上面的音频解码器示例，已从新版本中移除。

在许多情况下，使用迭代器的 Rust 代码，都会编译成与咱们手动编写的同样汇编代码。诸如循环展开及消除对数组访问的边界检查等优化，会得以应用而使生成的代码极其高效。现在咱们知道了这点，就可以放心地使用迭代器和闭包了！他们让代码看起来更高级，却不会因为这样做而带来运行时的性能损失。



# 本章小结

闭包与迭代器属于 Rust 受函数式编程思想启发的两项特性。他们贡献了 Rust 以底层性能，清晰地表达高级别概念的能力。闭包与迭代器的实现方式，确保了运行时性能不受影响。这属于 Rust 致力于提供零成本抽象目标的一部分。

现在我们已经改进了 I/O 项目的表达能力，我们来看看 `cargo` 的更多特性，这些特性将帮助我们与世界分享项目。


（End）


