performance - L2 取指令未命中远高于 L1 取指令未命中

标签 performance intel cpu-architecture cpu-cache perf

我正在生成一个综合 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() 中以随机顺序调用.我正在用 -O0 在 CentOS 7 下用 gcc 4.8.5 编译生成的代码.代码在装有 2x Intel Xeon E5-2630v3(Haswell 架构)的双插槽机器上运行。

我感兴趣的是在分析 时了解 perf 报告的与指令相关的计数器。从 C 代码编译的二进制文件 (不是 Python 脚本,它仅用于自动生成代码)。特别是,我正在观察以下计数器 perf stat :
  • 使用说明
  • L1-icache-load-misses (指令获取错过 L1,也就是 Haswell 上的 r0280)
  • r2424 , L2_RQSTS.CODE_RD_MISS (指令获取未命中 L2)
  • rf824 , L2_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 流送器
  • 启用 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
    

    现在, L2_RQSTS.ALL_PF 似乎表明正在发生更多事情,尽管我预计预取器会更加激进,但我认为由于跳跃密集型工作负载和数据预取器没有太多可做的事情,指令预取器受到了严重的考验面对这样的工作量。但是, L2_RQSTS.CODE_RD_MISS 在启用预取器的情况下仍然太高。

    所以,总而言之,我的问题是:

    禁用硬件预取器, L2_RQSTS.CODE_RD_MISS 好像比高很多L1-icache-load-misses .即使启用了硬件预取器,我仍然无法解释。如此高的 计数背后的原因是什么? L2_RQSTS.CODE_RD_MISS 相比L1-icache-load-misses ?

    最佳答案

    指令预取器可以生成的请求不计为对 L1I 缓存的访问,但计为更高编号内存级别(例如 L2)的代码提取请求。这通常适用于所有带有指令预取器的英特尔微架构。 L2_RQSTS.CODE_RD_MISS计算来自 L1I 的请求和预取请求。需求请求由 IFU 中的多路复用单元生成,该单元从流水线中可能改变流的不同单元(例如分支预测单元)中选择目标提取线性地址。如果可能,预取请求由 L1I 指令预取器在 L1I 未命中时生成。
    通常,预取请求的数量几乎与 L1I 未命中的数量成正比。对于从可缓存内存类型的内存区域获取指令,以下公式成立:ICACHE.MISSES <= L2_RQSTS.CODE_RD_MISS + L2_RQSTS.CODE_RD_HIT我不确定这个公式是否也适用于不可缓存的获取请求。我没有在那种情况下测试它。我知道这些请求算作 ICACHE.MISSES ,但不确定其他事件。
    在您的情况下,大多数指令提取将在 L1I 和 L2 中丢失。您有 10,000 个函数,每个函数几乎完全跨越 2 个 64 字节缓存行(here 是只有两个函数的版本),因此代码大小比 Haswell 上可用的 256 KiB L2 大得多。这些函数以非连续和可预测的顺序调用,因此 L1I 和 L2 预取器不会有太大帮助。唯一值得注意的异常(exception)是返回,所有这些都将使用 RSB 机制正确预测。
    10,000 个函数中的每一个都在循环中被调用 100,000 次。大多数获取请求都是针对这些函数占用的行。有用的取指令请求总数约为每个函数 2 行 * 10,000 个函数 * 100,000 次迭代 = 2,000,000,000 行,其中大部分将在 L1I 和 L2 中丢失(但可能在第一次冷迭代后在 L3 中命中)。数百万个其他请求将针对循环体占用的行。您的测量结果表明,L1I 中未命中的指令提取大约多出 30%。这是因为分支错误预测,这会导致对错误行的提取请求,这些行甚至可能不在 L1I 和/或 L2 中。每个 L1I 未命中都可能触发预取,因此 L2 指令提取在 L1I 未命中数的两倍以内是正常的。这与您的数字一致。
    在我的双功能 version ,我计算每个调用的函数有 24 条指令,所以我预计退役指令的总数大约为 240 亿条,但你得到了 250 亿条。要么我不知道如何计数,要么由于某种原因每个函数有 25 条指令。

    关于performance - L2 取指令未命中远高于 L1 取指令未命中,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41910227/

    相关文章:

    java - 重写与 if 语句的性能

    CPU 和数据对齐

    c++ - STLPort、英特尔编译器、构建错误(尽管应用运行良好!)

    linux - 测量时间 : differences among gettimeofday, TSC 和时钟滴答

    visual-studio-2012 - 为什么在这个例子中预取加速不是更大?

    assembly - 如何动态地将分支目标提示到 x64 CPU?

    c++ - 为什么 for 循环体中的一个基本算术运算执行得比两个算术运算慢?

    performance - Go - 在用于负载测试的高性能 http 客户端中,如何阻止/忽略所有 cookie?

    performance - 使用 Graphite+Grafana 测量服务的正常运行时间

    c++ - 如何比较 C++ 中 log() 和 fp 除法的性能?