我正在生成一个合成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 program ,它由许多随机命名的虚函数组成,这些函数又在 main() 中以随机顺序调用 . 我正在使用 -O0 在CentOS 7下使用gcc 4.8.5编译生成的代码 . 该代码在配备2x Intel Xeon E5-2630v3(Haswell架构)的双插槽机器上运行 .

我感兴趣的是在分析 binary compiled from the C code (不是Python脚本,仅用于自动生成代码)时理解perf报告的与指令相关的计数器 . 特别是,我正在使用 perf stat 观察以下计数器:

  • instructions

  • L1-icache-load-misses (指令提取错过L1,又名Haswell上的r0280)

  • r2424L2_RQSTS.CODE_RD_MISS (指令提取错过L2)

  • rf824L2_RQSTS.ALL_PF (所有L2硬件预取程序请求,包括代码和数据)

我首先在BIOS中禁用了所有硬件预取程序的代码,即

  • MLC Streamer禁用

  • 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 Streamer已启用

  • MLC Spatial Prefetcher已启用

  • 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

现在, L2_RQSTS.ALL_PF 似乎表明发生了更多的事情,虽然我预计预取器会更具攻击性,但我认为由于跳跃密集型工作负载和数据预取器没有,预编译器严重受到测试与这种工作量有很大关系 . 但同样, L2_RQSTS.CODE_RD_MISS 仍然太高,启用了预取器 .

所以,总结一下,我的问题是:

禁用硬件预取程序后, L2_RQSTS.CODE_RD_MISS 似乎远高于 L1-icache-load-misses . 即使启用了硬件预取程序,我仍然无法解释它 . 与 L1-icache-load-misses 相比,如此高的 L2_RQSTS.CODE_RD_MISS 背后的原因是什么?