是否可以考虑所有缓存未命中?

时间:2015-04-26 18:41:58

标签: performance caching performancecounter perf

我试图了解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。

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

3 个答案:

答案 0 :(得分:2)

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

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

给出一个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

我告诉它计算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映射到计数最后一级高速缓存未命中的硬件事件。因此,这类似于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 :(得分:2)

这不是一个容易的话题,但是,如果您想对(例如)访问数组所产生的高速缓存未命中计数,那么您应该从这里开始。

存在很多陷阱,但是最有可能导致洞察的方法是从一个程序开始,该程序分配一个数组,将值存储到该数组中,然后以可编程的次数读取该数组。

将值存储到数组中对于创建虚拟页面到物理页面的映射是必要的。由于操作系统在初始化这些页面时使用了一些技巧,例如,从映射到零填充页面开始并将访问权限设置为“写入时复制”,因此本节的性能计数器结果可能难以理解。 / p>

实例化页面后,读取的性能计数可能更有意义。我使用了可编程的读取次数,以便可以得出20个读取和10个读取的计数器值之间的差值(例如)。 阵列大小应选择为比要测试的级别上的可用缓存大得多。

不幸的是,“性能”使在硬件级别(这是唯一重要的级别)上找出性能计数器中实际编程的内容变得相对困难。事件越“通用”,就越难猜测实际测量的内容。...在我最近的基于Intel的系统上,“性能列表”给出了一长串(> 3600行)可用事件。从标为“高速缓存:”的部分开始的事件是硬件事件的直接翻译,《英特尔体系结构软件开发人员手册》第3卷第19章中介绍了这些事件。

您正确地担心如何对硬件预取进行计数是正确的。在最近的英特尔架构中,报告缓存访问的事件通常可以配置为对需求访问,硬件预取或两者进行计数。报告加载指令 的源位置的事件不会提供任何关于硬件预取在何处找到数据的见识-只是在执行加载操作时它与处理器之间的距离。 >

我发现事件“ l1d.replacements”是最近的英特尔处理器上的可靠L1数据高速缓存未命中指示器。它仅计算移入L1数据高速缓存中的所有高速缓存行(无论是由于加载,存储,预取等)。在体系结构的另一端,DRAM计数器(例如,“ uncore_imc_0 / cas_count_read /”)也是可靠的,但是由于系统中的任何其他活动而受到污染。 “双面”高速缓存(例如L2和L3)的计数器更容易造成混乱,因为并不总是清楚事件是否在对从一侧或另一侧或两者发送的高速缓存行进行计数(例如“ l2_lines_in”)。所有”)。通过一些精心控制的实验,通常可以在这些中间级别上找到可靠且可理解的事件的子集。并非总是可能找到足够的可靠计数器来对存储器层次结构的每个级别上的所有流量进行完整的计算,但这是一个较长的故事。...

答案 2 :(得分:0)

进程内存空间不仅仅是关于你的代码,还有不同的来源,如堆,堆栈,数据段也会导致缓存未命中。

process memory space http://www.tenouk.com/ModuleZ_files/cmemory003.png

我认为你不能估计缓存未命中数,就像你无法预测多线程程序中每个线程的运行顺序一样。

但是,缓存未命中分析对于找出并定位false sharing很有用。以下是您可以参考的一些有用链接:

  1. http://igoro.com/archive/gallery-of-processor-cache-effects/
  2. http://qqibrow.github.io/CPU-Cache-Effects-and-Linux-Perf/