You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

clflush未必总能引发L1i缓存缺失?还是PMU计数器存在误差?

关于L1指令缓存缺失计数与clflush行为的疑问

问题背景

我编写了一段汇编程序,循环1000万次调用fun函数。fun函数会先执行clflush not_fun刷新not_fun所在的缓存行,再调用not_fun。为确保两个函数不在同一缓存行,我将它们按512字节对齐,理论上每次调用fun都应该触发L1指令缓存(L1i)缺失。

汇编程序

.section .data

.section .text

.global fun
.global not_fun

.align 512
not_fun:
        nop
        ret

.align 512
fun:
        clflush not_fun
        mfence
        call not_fun
        ret

.global _start
_start:
        mov $10000000, %rcx

loop:
        call fun
        dec %rcx
        jnz loop       

        xor %rdi,%rdi
        mov $60, %rax
        syscall

测试步骤与结果

使用perf stat统计frontend_retired.l1i_miss计数器(Intel手册定义:已退役指令中遭遇L1指令缓存真实缺失的取指操作,同一缓存行的重复请求不计数),执行命令及结果如下:

$ as fun.S -o fun.o
$ g++ fun.o -std=c++17 -nostdlib -no-pie -O3 -Wall -Werror -fno-omit-frame-pointer
$ sudo perf stat -r 5 -e cycles,instructions,frontend_retired.l1i_miss taskset -c 0 nice -n -20 ./a.out

 Performance counter stats for 'taskset -c 0 nice -n -20 ./a.out' (5 runs):

    7,86,64,83,671      cycles                                                                  ( +-  0.33% )
      10,39,34,470      instructions                     #    0.01  insn per cycle              ( +-  0.02% )
         67,94,237      frontend_retired.l1i_miss                                               ( +-  0.99% )

           1.81076 +- 0.00394 seconds time elapsed  ( +-  0.22% )

实际统计的L1i缺失次数约为679万,远小于1000万次调用次数。同一二进制文件多次运行结果稳定(波动±1万),但重编译后(仅对齐填充不同)结果差异极大(从200万到1000万不等),我想知道:是计数器不准确,还是clflush并未每次都成功刷新not_fun?


问题分析与解答

1. 计数器准确性排除

frontend_retired.l1i_miss计数器本身是可靠的,但要明确它的计数逻辑:仅当取指操作因L1i缺失导致停顿,且对应的指令最终完成退役时,才会被计数。如果缓存行在指令执行前已被预取填充,即使之前clflush失效了缓存行,也不会触发计数。

2. clflush与预取的冲突

  • clflush的作用是将目标缓存行写回内存并标记为失效,但它无法阻止CPU的指令预取器。mfence仅保证内存读写操作的可见性顺序,对前端的指令预取行为没有约束。在clflush执行后、call not_fun之前,CPU的指令预取器可能已经提前将not_fun的指令加载到L1i中,直接抵消了clflush的效果。
  • 此外,L2或LLC的硬件预取器也可能在clflush失效缓存行后,重新预取该缓存行,导致L1i中再次出现有效缓存行。

3. 重编译地址变化的影响

重编译后函数的虚拟地址会发生变化,而CPU的预取器(如基于指令地址的IP预取器)会根据地址模式预测预取目标。如果not_fun的新地址恰好匹配预取器的预测模式,预取命中率会显著提升,L1i缺失次数就会减少;反之则缺失次数接近理论值。这就是重编译后结果波动极大的核心原因。

4. 验证建议

  • 在mfence后添加一段无意义的指令序列(如10个nop),拉长clflush到call not_fun的时间间隔,降低预取提前触发的概率,观察缺失次数是否接近1000万。
  • 使用perf mem record ./a.out + perf mem report分析具体的缺失地址,确认是否全部来自not_fun的入口,排除其他代码的缺失干扰。
  • 通过BIOS或wrmsr指令禁用CPU的预取功能,再测试缺失次数是否符合预期。

内容的提问来源于stack exchange,提问作者Box Box Box Box

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 05:14:52