Rust 的数组边界检查会影响性能吗?

Does Rust's array bounds checking affect performance?

我来自 C,我想知道 Rust 的边界检查是否会影响性能。每次访问都可能需要一些额外的汇编指令,这在处理大量数据时可能会造成伤害。

另一方面,处理器性能中代价高昂的是内存,因此更多的算术汇编程序指令可能不会有什么坏处,但在加载高速缓存行之后,顺序访问应该非常快可能很重要。

有人对此进行了基准测试吗?

不幸的是,边界检查的成本不是一件容易估计的事情。它肯定不是 "one cycle per check",也不是任何容易猜到的成本。它产生非零影响,但可能微不足道。

理论上,可以通过修改 Rust 禁用它们和 运行 大规模生态系统测试来衡量对 Vec 等基本类型进行边界检查的成本。这会给出某种经验法则,但如果不这样做,很难知道这会接近百分之十还是十分之一的开销。

不过,有一些方法可以比计时和猜测做得更好。这些经验法则主要适用于台式机-class 硬件;低端硬件或针对不同细分市场的东西将具有不同的特征。

如果您的索引是从容器大小派生的,编译器可能很有可能能够消除边界完全检查。此时,发布版本中边界检查的唯一成本是它间歇性地干扰优化,可能,但通常不会,阻碍其他优化。

如果您的代码是分支的、内存访问繁重或难以优化,而要检查的边界很容易访问,边界检查很有可能会管理主要发生在 CPU 的空闲带宽中,分支预测特别有帮助,在这种情况下,总体成本将特别小,特别是与其余代码的成本相比。

如果您要检查的边界位于几层指针之后,您会遇到内存延迟问题似是而非,并且会受到相应的伤害。然而,CPU 中的推测和预测机制也有可能设法隐藏这一点;这是非常依赖于上下文的。如果您正在引用内部数据,而不是在边界检查的同时取消引用它,则此风险会放大。

如果您的边界检查处于不会使核心饱和的紧密算术循环中,您不太可能直接损害吞吐量,除非阻碍其他编译器优化。然而,阻碍其他编译器优化可能是任意糟糕的,从没有区别到阻止 SIMD 并导致 10 倍减速。

如果您的边界检查处于一个紧密的算术循环中,确实使核心饱和,您承担上述风险每次边界检查有大约半个周期的直接执行惩罚。

如果您的代码大到足以对指令缓存造成压力,那么您需要担心对代码大小的影响。这通常是适度的,但特别难以衡量运行时的影响。

Peter Cordes 在评论中进一步补充了一些要点。首先,边界检查意味着加载和存储,因此您将成为 运行 最有可能成为 issue/rename 瓶颈的混合加载。其次,即使是并行执行的预测分支也会占用预测器的资源,这会导致其他分支的预测更差。

这似乎令人生畏,事实确实如此。这就是为什么在与您和您的代码相关的级别上衡量和了解您的性能很重要。

情况也是如此,因为 Rust "born" 具有边界检查,它已经产生了降低成本的方法,例如普遍的零成本引用、迭代器(吸收,但实际上不删除、边界检查)和一组不寻常的实用函数。如果您发现自己遇到了病态案例,Rust 还提供了不安全的逃生通道。