prefetch 在 c++ 中无标准语法,需用编译器内置函数如 __builtin_prefetch;其三个参数为地址、读写提示(0 读/1 写)、局部性(0–3),常用 __builtin_prefetch(ptr, 0, 3) 或 (ptr, 0, 0);预取须提前足够步数(如顺序扫描提前 8–16 元素)以匹配访存延迟。

prefetch 指令在 C++ 中没有标准语法,得靠编译器内置函数
你不能直接写 prefetch 当作 C++ 关键字用——C++ 标准里压根没这玩意。真正能触发 CPU 预取行为的,是编译器提供的内置函数(intrinsics),比如 GCC/Clang 的 __builtin_prefetch,或 MSVC 的 _mm_prefetch。它们最终被翻译成 x86 的 PREFETCHNTA、PREFETCHT0 等指令。
常见错误现象:写了 prefetch(ptr) 却发现性能没变甚至更差,大概率是因为没传对参数,或者预取时机/地址根本没对上热数据流。
-
__builtin_prefetch有三个参数:addr(地址)、rw(读/写提示,0=读,1=写)、locality(局部性提示,0–3,影响缓存层级) - 多数场景只用读 + 中等局部性:
__builtin_prefetch(ptr, 0, 3)(T0,加载到 L1/L2)或__builtin_prefetch(ptr, 0, 0)(NTA,绕过缓存直写内存,适合大数组顺序扫描) - 传入空指针、未对齐地址、或已释放内存的地址,不会崩溃,但预取失效,还白占流水线资源
预取位置必须比实际访问提前足够多的迭代步数
预取不是“越早越好”,而是要匹配 CPU 访存延迟与计算延迟的差值。典型现代 x86 处理器上一次 L3 缺失可能耗 200+ 周期,而一段简单循环体可能只要 10–20 周期。如果只提前 1 步预取,数据根本来不及进缓存。
使用场景:遍历大数组做计算(如图像处理、矩阵向量化);结构体数组按字段聚合访问(SoA);链表跳转前预取下个节点。
立即学习“C++免费学习笔记(深入)”;
- 对步长为 1 的顺序扫描,通常提前 8–16 个元素较稳,例如:
for (int i = 0; i < n; ++i) { if (i + 12 < n) __builtin_prefetch(&arr[i + 12], 0, 3); process(arr[i]); } - 若循环体含分支或长延迟指令(如除法、函数调用),需加大提前量;若用 SIMD 批处理,可按批预取(如每次预取 4 个 float4 结构)
- 别在循环开头无条件预取
&arr[0]——它大概率已在缓存里;也别对每个i都预取i+1,开销反超收益
不同 prefetch 提示对缓存层级和驱逐策略影响很大
locality 参数不是“越高越好”。它告诉 CPU 这个数据后续是否会被频繁复用,从而决定放进哪级缓存、是否挤走其他行。选错会导致本该常驻的数据被踢出,或不该进 L1 的大数据块塞爆缓存。
性能影响明显:在 256KB L2 容量的 CPU 上,对 1GB 数组用 locality=3 可能引发持续的 L2 驱逐抖动;而用 locality=0(NTA)则让预取数据不进缓存,仅填入填充缓冲区(fill buffer),避免污染。
-
locality=0:NTA(Non-Temporal Align),适合单次遍历的大数据流,如 memcpy、filter 扫描 -
locality=3:T0(Temporal 0),预期很快重用,优先进 L1;适合小工作集、随机访存前的 hint(如树节点遍历) - ARM 上对应的是
__builtin_arm_prefetch,参数含义不同,is_write和cache_level是分开的,混用 x86 习惯会出错
用 perf 或 VTune 验证预取是否真起作用
光看 runtime 下降不靠谱。预取可能掩盖了别的瓶颈(比如 ALU 单元争用),也可能只是让 cache-miss 转成了 TLB-miss 或 page-fault。真实收益得看硬件事件计数器。
容易踩的坑:在 debug 模式下测预取效果;或用小数据集(全在 L3 里)验证,根本触发不了缺页路径。
- 关键指标:
perf stat -e cycles,instructions,cache-misses,mem-loads,mem-stores,l1d.replacement - 有效预取的表现:cache-misses ↓、l1d.replacement ↓、mem-loads 的平均延迟 ↓,同时 cycles/instruction 不劣化
- 如果
mem-loads暴涨但cache-misses不降,说明预取地址算错了,CPU 在反复预取无效区域
预取不是银弹,它把时间换空间的权衡显式暴露给了程序员——你得清楚知道数据布局、访存模式、目标 CPU 的缓存拓扑,否则很容易搬起石头砸自己的脚。











