perf 可以解释所有缓存未命中吗?

use*_*469 2 performance caching performancecounter perf

我试图了解 perf 记录的缓存未命中。我有一个最小的程序:

int main(void)
{
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

如果我编译这个:

gcc -std=c99 -W -Wall -Werror -O3 -S -o test.S test.c
Run Code Online (Sandbox Code Playgroud)

我得到了一个预期的小程序:

        .file   "test.c"
        .section        .text.startup,"ax",@progbits
        .p2align 4,,15
        .globl  main
        .type   main, @function
main:
.LFB0:
        .cfi_startproc
        xorl    %eax, %eax
        ret
        .cfi_endproc
.LFE0:
        .size   main, .-main
        .ident  "GCC: (Debian 4.7.2-5) 4.7.2"
        .section        .note.GNU-stack,"",@progbits
Run Code Online (Sandbox Code Playgroud)

只有两条指令, xorland ret,程序的大小应该小于一个缓存行,所以我希望如果我运行perf -e "cache-misses:u" ./test我应该只看到一个缓存未命中。但是,我看到的是 2 到 ~400。同样,perf -e "cache-misses" ./test结果是 ~700 到 ~2500。

这只是一个估计计数的情况,还是缓存未命中发生的方式使对它们的推理近似?例如,如果我生成并读取内存中的一个整数数组,我是否可以推理预取(顺序访问应该允许完美预取)或者还有其他东西在起作用吗?

Pet*_*des 5

您创建了一个main而不是_start,并且可能将它构建到一个动态链接的可执行文件中!!所以有所有的 CRT 启动代码、初始化 libc 和几个系统调用。运行strace ./test并查看它进行了多少系统调用。(当然,用户空间中有很多不涉及系统调用的工作)。

更有趣的是静态链接的可执行文件,它只是从入口点使用指令进行_exit(0)exit_group(0)系统调用。syscall_start

给定一个exit.s包含这些内容:

mov $231, %eax
syscall
Run Code Online (Sandbox Code Playgroud)

将其构建为静态可执行文件,因此这两条指令是唯一在用户空间中执行的指令:

$ gcc -static -nostdlib exit.s
/usr/bin/ld: warning: cannot find entry symbol _start; defaulting to 0000000000401000
  # the default is fine, our instructions are at the start of the .text section

$ perf stat -e cache-misses:u ./a.out 

 Performance counter stats for './a.out':

                 6      cache-misses:u                                              

       0.000345362 seconds time elapsed

       0.000382000 seconds user
       0.000000000 seconds sys
Run Code Online (Sandbox Code Playgroud)

我告诉它计数cache-misses:u只测量用户空间缓存未命中,而不是进程正在运行的核心上的所有内容。(这将包括在进入用户空间之前和处理exit_group()系统调用时内核缓存未命中。以及潜在的中断处理程序)。

(当特权级别为用户、内核或两者时,PMU 中的硬件支持对事件进行计数。因此,我们应该期望计数最多减少 1 或 2,从计算从内核-> 用户转换期间完成的工作或 user->kernel.(更改 CS,可能导致从 GDT 加载由新 CS 值索引的段描述符)。


但什么事件cache-misses实际上算数?

Linux perf 如何计算缓存引用和缓存未命中事件 说明:

perf显然映射cache-misses到计算最后一级缓存未命中的硬件事件。所以它类似于DRAM访问的数量。

多次尝试访问 L1d 或 L1i 缓存中的同一行,而 L1 未命中已经未完成,只会增加另一件事,等待相同的传入缓存行。所以它不计算必须等待缓存的负载(或代码提取)。多个负载可以合并为一个访问。

但也要记住,代码提取需要通过 iTLB,触发页面遍历。 Page-walk 加载被缓存,即它们通过缓存层次结构获取。因此,cache-misses如果他们确实错过了,他们会被事件计算在内。

程序的重复运行可能导致0缓存未命中事件。 可执行二进制文件是一个文件,该文件由页面缓存缓存(操作系统的磁盘缓存)。该物理内存被映射到运行它的进程的地址空间。它当然可以在整个进程启动/停止过程中在 L3 中保持热度。更有趣的是,显然页表也很热。(不是字面上的“保持”热;我假设内核每次都必须写一个新的。但据推测,page-walker 至少在 L3 缓存中被击中。)

或者至少其他导致“额外”cache-miss事件的事情不必发生。

我曾经perf stat -r16运行它 16 次并显示 mean +stddev

$ perf stat -e instructions:u,L1-dcache-loads:u,L1-dcache-load-misses:u,cache-misses:u,itlb_misses.walk_completed:u -r 16 ./exit

 Performance counter stats for './exit' (16 runs):

                 3      instructions:u                                              
                 1      L1-dcache-loads                                             
                 5      L1-dcache-load-misses     #  506.25% of all L1-dcache hits    ( +-  6.37% )
                 1      cache-misses:u                                                ( +-100.00% )
                 2      itlb_misses.walk_completed:u                                   

         0.0001422 +- 0.0000108 seconds time elapsed  ( +-  7.57% )
Run Code Online (Sandbox Code Playgroud)

注意缓存未命中的 +-100%。

我不知道为什么我们有 2 个 itlb_misses.walk_completed 事件,而不仅仅是 1 个。计数itlb_misses.miss_causes_a_walk:u反而让我们4始终如一。

减少到-r 1并使用手动向上箭头重复运行,cache-misses在 3 和 13 之间反弹。系统大部分处于空闲状态,但有一些后台网络流量。

我也不知道为什么任何东西都显示为 L1D 负载,或者为什么一次负载会出现 6 次未命中。但是哈迪的回答说perfL1-dcache-load-misses 事件实际上很重要L1D.REPLACEMENT,所以页面遍历可以解释这一点。虽然L1-dcache-loads计数MEM_INST_RETIRED.ALL_LOADSmov-immediate不是负担,我也不会想到syscall是。但也许是这样,否则硬件会错误地计算内核指令,或者某处有一个 off-by-1。