我正在生成一个综合 C 基准测试,旨在通过以下 Python 脚本导致大量指令获取丢失:
#!/usr/bin/env python
import tempfile
import random
import sys
if __name__ == '__main__':
functions = list()
for i in range(10000):
func_name = "f_{}".format(next(tempfile._get_candidate_names()))
sys.stdout.write("void {}() {{\n".format(func_name))
sys.stdout.write(" double pi = 3.14, r = 50, h = 100, e = 2.7, res;\n")
sys.stdout.write(" res = pi*r*r*h;\n")
sys.stdout.write(" res = res/(e*e);\n")
sys.stdout.write("}\n")
functions.append(func_name)
sys.stdout.write("int main() {\n")
sys.stdout.write("unsigned int i;\n")
sys.stdout.write("for(i =0 ; i < 100000 ;i ++ ){\n")
for i in range(10000):
r = random.randint(0, len(functions)-1)
sys.stdout.write("{}();\n".format(functions[r]))
sys.stdout.write("}\n")
sys.stdout.write("}\n")
代码的作用只是生成一个C程序它由许多随机命名的虚拟函数组成,这些函数依次以随机顺序调用main()
。我正在 CentOS 7 下使用 gcc 4.8.5 编译生成的代码-O0
。该代码在配备 2 个 Intel Xeon E5-2630v3(Haswell 架构)的双插槽计算机上运行。
我感兴趣的是了解 perf 在分析时报告的与指令相关的计数器从 C 代码编译的二进制文件(不是Python脚本,仅用于自动生成代码)。特别是,我正在观察以下计数器perf stat
:
- 指示
-
L1-icache-加载未命中(指令获取错过了 L1,又名 Haswell 上的 r0280)
-
r2424, L2_RQSTS.CODE_RD_MISS(缺少 L2 的指令获取)
-
rf824, L2_RQSTS.ALL_PF(所有 L2 硬件预取器请求,包括代码和数据)
我首先分析了 BIOS 中禁用所有硬件预取器的代码,即
- MLC 流媒体已禁用
- MLC 空间预取器已禁用
- DCU 数据预取器已禁用
- DCU 指令预取器禁用
结果如下(进程固定到第二个 CPU 的第一个核心和相应的 NUMA 域,但我想这没有太大区别):
perf stat -e instructions,L1-icache-load-misses,r2424,rf824 numactl --physcpubind=8 --membind=1 /tmp/code
Performance counter stats for 'numactl --physcpubind=8 --membind=1 /tmp/code':
25,108,610,204 instructions
2,613,075,664 L1-icache-load-misses
5,065,167,059 r2424
17 rf824
33.696954142 seconds time elapsed
考虑到上面的数字,我无法解释 L2 中如此大量的取指令未命中。我已禁用所有预取器,并且L2_RQSTS.ALL_PF证实了这一点。但为什么我看到 L2 中的取指未命中次数是 L1i 中的两倍?在我的(简单)心理处理器模型中,如果在 L2 中查找一条指令,那么它之前一定在 L1i 中查找过。显然我错了,我错过了什么?
然后,我尝试在启用所有硬件预取器的情况下运行相同的代码,即
- MLC 流媒体启用
- 启用 MLC 空间预取器
- 启用 DCU 数据预取器
- DCU 指令预取器启用
结果如下:
perf stat -e instructions,L1-icache-load-misses,r2424,rf824 numactl --physcpubind=8 --membind=1 /tmp/code
Performance counter stats for 'numactl --physcpubind=8 --membind=1 /tmp/code':
25,109,877,626 instructions
2,599,883,072 L1-icache-load-misses
5,054,883,231 r2424
908,494 rf824
Now, L2_RQSTS.ALL_PF似乎表明正在发生更多事情,尽管我预计预取器会更加激进,但我认为由于跳跃密集型工作负载和数据预取器没有太多作用,指令预取器受到了严格的考验面对这样的工作量。但话又说回来,L2_RQSTS.CODE_RD_MISS启用预取器后仍然太高。
所以,总而言之,我的问题是:
禁用硬件预取器后,L2_RQSTS.CODE_RD_MISS似乎比L1-icache-加载未命中。即使启用了硬件预取器,我仍然无法解释它。如此高的计数背后的原因是什么?L2_RQSTS.CODE_RD_MISS相比L1-icache-加载未命中?