Perf可以解释所有高速缓存未命中的原因吗?

问题描述 投票:0回答:2

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

int main(void)
{
    return 0;
}

如果我编译此:

gcc -std=c99 -W -Wall -Werror -O3 -S -o test.S test.c

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

        .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

仅使用xorlret这两个指令,程序的大小应小于高速缓存行,因此我希望如果运行perf -e "cache-misses:u" ./test,则只会看到单个高速缓存未命中。但是,我反而看到2到〜400之间。同样,perf -e "cache-misses" ./test导致〜700至〜2500。

这是仅是一次估算计数的情况,还是有关高速缓存未命中的方式是否使估算值近似?例如,如果我生成然后读取内存中的整数数组,我是否可以推断预取(顺序访问应允许进行完美的预取)还是在起作用?

performance caching performancecounter perf
2个回答
0
投票

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

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

给出具有以下内容的exit.s

mov $231, %eax
syscall

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

$ 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

我告诉它只计算user-space高速缓存未命中,而不是运行进程的核心上的所有内容,只计算cache-misses:u。 (这包括进入用户空间之前和处理exit_group()系统调用时内核高速缓存未命中。以及可能的中断处理程序。)>

(PMU中提供了硬件支持,可以在特权级别为用户,内核或同时为两者时对事件进行计数。因此,我们希望计数从内核转换过程中完成的工作计数最多相差1或2。 >用户或用户->内核(更改CS,可能会导致GDT加载由新CS值索引的段描述符)。


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

How does Linux perf calculate the cache-references and cache-misses events解释:

perf显然将cache-misses映射到计数last-level

高速缓存未命中的硬件事件。因此,这类似于DRAM访问的次数。

[L1未命中的情况下,多次尝试访问L1d或L1i高速缓存中的同一行只会增加另一件事,等待相同的传入高速缓存行。因此,它不计算必须等待缓存的负载(或代码获取)。多个负载可以合并为一个访问。

[[但还请记住,代码获取需要通过iTLB,从而触发页面遍历。

缓存页面遍历负载,即通过缓存层次结构来获取它们。因此,如果确实错过了,它们就会被cache-misses事件计数。

程序的重复运行可能会导致0缓存丢失事件。

可执行二进制文件是一个文件,并且该文件由页面缓存进行缓存(操作系统的磁盘缓存)。该物理内存被映射到运行它的进程的地址空间。在整个过程启动/停止过程中,L3肯定可以保持高温。更有趣的是,页表显然也很热。 (并不是字面上的“保持”不变;我认为内核每次都必须写一个新的。但是想必page-walker至少在L3缓存中命中了。)
或者至少不需要引起“额外” cache-miss事件的任何其他原因。

我用perf stat -r16运行了16次,并显示了平均值+ 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% )

请注意缓存丢失时为+ -100%。

我不知道为什么我们有2个itlb_misses.walk_completed事件,而不仅仅是1个。计数itlb_misses.miss_causes_a_walk:u总是给我们4

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

我也不知道为什么任何东西都显示为L1D负载,或者一次负载怎么可能出现6次未命中。但是Hadi的回答是perf的L1-dcache-load-misses事件实际上是L1D.REPLACEMENT,因此,页面遍历可以解释这一点。 L1-dcache-loads计为MEM_INST_RETIRED.ALL_LOADSmov-immediate不是负载,我也不会想到syscall也是。但是也许是这样,否则硬件会错误地计数内核指令,或者某个地方存在偏离1的位置。


-1
投票
进程内存空间不仅与您的代码有关,还有一些不同的来源,例如堆,堆栈,数据段也将导致高速缓存未命中。
© www.soinside.com 2019 - 2024. All rights reserved.