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。
这只是一个估计计数的情况,还是缓存未命中发生的方式使对它们的推理近似?例如,如果我生成并读取内存中的一个整数数组,我是否可以推理预取(顺序访问应该允许完美预取)或者还有其他东西在起作用吗?
您创建了一个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_LOADS。 mov-immediate不是负担,我也不会想到syscall是。但也许是这样,否则硬件会错误地计算内核指令,或者某处有一个 off-by-1。