为什么在 Rust 中遍历 240 个元素的数组会变慢?深度解析

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

240 元素的奥秘:为什么你的 Rust 代码会突然变慢?

\n\n

想象一下这个场景:你正在用 Rust 编写高性能代码,一切运行如飞。你在 200 个元素的数组上测试算法——效果很好。当你再增加 40 个元素(总共 240 个)时——性能却下降了 2-3 倍。听起来像魔法?不,这是物理定律。更准确地说——是现代处理器的架构。

\n\n

在 Stack Overflow 上,问题 "Why is there a large performance impact when looping over an array with 240 or more elements?" 获得了数千次浏览,并成为了经典。在 2025 年,随着新微架构的出现和缓存层次结构的复杂化,理解这个问题对于任何 Rust 开发者来说都至关重要。

\n\n

在本文中,我们不仅仅是复述答案。我们将展示这在 Rust 中是如何体现的,如何诊断问题,以及最重要的是——如何利用 2025 年的现代实践来避免它。

\n\n

问题的根源:缓存未命中(Cache Misses)

\n\n

处理器不直接与内存(RAM)交互。它使用 缓存——芯片上的超高速内存。数据以称为 缓存行(通常为 64 字节)的块加载到缓存中。

\n\n

数字 240 的魔力与 L1 缓存(通常数据缓存为 32 KB)的大小以及元素的大小有关。如果数组是 i32(4 字节),那么 240 个元素占用 960 字节。看起来这可以放入 L1 缓存。但问题在于,算法可能同时操作两个或三个数组,或者编译器无法有效地向量化代码,从而导致 缓存未命中。

\n\n

在原始的 C++ 问题中,阈值恰好是 240 个元素,因为它触及了预取(prefetching)特性以及 L1/L2 缓存大小的限制。

\n\n

这在 Rust 中是如何体现的?

\n\n

让我们模拟一下这个场景。我们将对数组元素求和,但使用一个会破坏缓存的“步长”。

\n\n
use std::time::Instant;\n\nfn main() {\n    // 240 个元素的数组\n    let size = 240;\n    let data: Vec<i32> = (0..size).collect();\n    \n    // 冷启动\n    let start = Instant::now();\n    let sum: i32 = data.iter().sum();\n    let duration = start.elapsed();\n    println!("240 个元素(顺序访问): {:?}", duration);\n    \n    // 现在使用步长 64(缓存行大小)进行“糟糕”的访问\n    let mut sum2 = 0i32;\n    let start = Instant::now();\n    for i in (0..size).step_by(64) {\n        sum2 += data[i];\n    }\n    let duration2 = start.elapsed();\n    println!("240 个元素(步长 64): {:?}", duration2);\n}\n
\n\n

你会惊讶地发现,差异可能高达 10 倍!第一个循环使用 顺序访问——处理器会预测并提前加载缓存行。第二个循环是 随机访问,步长较大,会将数据从缓存中踢出。

\n\n

为什么恰好是 240?深层原因

\n\n

在 2025 年,处理器的 L1 数据缓存大小从 32 KB 到 64 KB 不等。但问题不仅仅在于大小。关键在于缓存的 组相联性。

\n\n

把缓存想象成一个有 64 个书架(组)的图书馆。每个书架上可以放 8 本书(路)。当处理器访问内存时,它会根据地址计算“书架编号”。如果书架上已经有 8 本书,就必须扔掉一本。

\n\n

对于大小为 2 的幂次倍的数组(例如 256),所有元素可能落入同一个“书架”,导致 缓存冲突未命中。240 是 256 减去 16,这打破了这种倍数关系,并且……由于预取的特性,反而使情况变得更糟。

\n\n

Rust 中的实际测试

\n\n
use std::time::Instant;\n\nfn bench_access(size: usize) {\n    let data: Vec<i32> = (0..size as i32).collect();\n    \n    // “糟糕”的模式:访问每第 16 个元素\n    let mut sum = 0i32;\n    let start = Instant::now();\n    for i in (0..size).step_by(16)                

博客

书籍推荐