Linux perf报告缓存未命中意外指令

Ste*_*wat 5 linux performance caching perf

我正在尝试将一些性能工程技术应用于Dijkstra算法的实现.为了找到(天真的和未经优化的)程序中的瓶颈,我正在使用该perf命令来记录缓存未命中的数量.相关的代码片段如下,它找到距离最小的未访问节点:

for (int i = 0; i < count; i++) {
    if (!visited[i]) {
        if (tmp == -1 || dist[i] < dist[tmp]) {
            tmp = i;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

对于LLC-load-misses度量标准,perf report显示程序集的以下注释:

       ?             for (int i = 0; i < count; i++) {                                                                                                                           ?
  1.19 ? ff:   add    $0x1,%eax                                                                                                                                                  ?
  0.03 ?102:   cmp    0x20(%rsp),%eax                                                                                                                                            ?
       ?     ? jge    135                                                                                                                                                        ?
       ?                 if (!visited[i]) {                                                                                                                                      ?
  0.07 ?       movslq %eax,%rdx                                                                                                                                                  ?
       ?       mov    0x18(%rsp),%rdi                                                                                                                                            ?
  0.70 ?       cmpb   $0x0,(%rdi,%rdx,1)                                                                                                                                         ?
  0.53 ?     ? jne    ff                                                                                                                                                         ?
       ?                     if (tmp == -1 || dist[i] < dist[tmp]) {                                                                                                             ?
  0.07 ?       cmp    $0xffffffff,%r13d                                                                                                                                          ?
       ?     ? je     fc                                                                                                                                                         ?
  0.96 ?       mov    0x40(%rsp),%rcx                                                                                                                                            ?
  0.08 ?       movslq %r13d,%rsi                                                                                                                                                 ?
       ?       movsd  (%rcx,%rsi,8),%xmm0                                                                                                                                        ?
  0.13 ?       ucomis (%rcx,%rdx,8),%xmm0                                                                                                                                        ?
 57.99 ?     ? jbe    ff                                                                                                                                                         ?
       ?                         tmp = i;                                                                                                                                        ?
       ?       mov    %eax,%r13d                                                                                                                                                 ?
       ?     ? jmp    ff                                                                                                                                                         ?
       ?                     }                                                                                                                                                   ?
       ?                 }                                                                                                                                                       ?
       ?             }   
Run Code Online (Sandbox Code Playgroud)

我的问题是:为什么jbe指令产生如此多的缓存未命中?如果我没有弄错的话,该指令根本不必从内存中检索任何内容.我认为它可能与指令缓存未命中有关,但即使只测量L1数据缓存未命中L1-dcache-load-misses,也表明该指令中存在大量缓存未命中.

这有点让我感到困惑.谁能解释这个(在我看来)奇怪的结果?先感谢您.

osg*_*sgx 6

关于你的例子:

高柜台前和高柜台有几条指令:

        ?       movsd  (%rcx,%rsi,8),%xmm0
   0.13 ?       ucomis (%rcx,%rdx,8),%xmm0
  57.99 ?     ? jbe    ff
Run Code Online (Sandbox Code Playgroud)

"movsd"将来自(%rcx,%rsi,8)(某些数组访问)的字加载到xmm0寄存器中,"ucomis"加载另一个字(%rcx,%rdx,8),并将其与xmm0寄存器中刚加载的值进行比较."jbe"是条件跳跃,取决于比较结果.

许多现代英特尔CPU(以及AMD可能也是)可以并且将把一些操作组合(realworldtech.com/nehalem/5"融合到单个uop,CMP + JCC中)"和cmp +条件跳转非常常见的指令要融合的组合(您可以使用Intel IACA模拟工具进行检查,对您的CPU使用ver 2.1).可以在perf/PMU/PEBS中错误地报告融合对,其中大多数事件偏向两个指令之一.

该代码可能意味着表达式"dist [i] <dist [tmp]"生成两个存储器访问,并且这两个值都用于ucomisjbe条件跳转(部分?)融合的指令中.dist [i]或dist [tmp]或两个表达式都会产生大量未命中.任何这样的未命中将阻止ucomis生成结果并阻塞jbe以给出下一条指令以执行(或退出预测的指令).因此,jbe可能会获得高计数器的所有名声,而不是真正的内存访问指令(对于像"缓存"响应这样的"远"事件,会对最后一次阻塞指令产生一些偏差).

您可以尝试将已访问的[N]和dist [N]数组合并到数组[N]中,struct { int visited; float dist}以便array[i].dist在您访问时强制进行预取,array[i].visited或者您可以尝试更改顶点访问顺序,或重新编号图顶点,或者执行某些软件预取下一个或多个元素(?)


关于perf名称问题和可能的非核心倾斜的通用事件.

perfLinux中的(perf_events)工具在调用时使用预定义的事件集perf list,并且某些列出的硬件事件可能无法实现; 其他映射到当前的CPU功能(并且一些映射不完全正确).有关真实PMU的一些基本信息在您的https://software.intel.com/sites/products/collat​​eral/hpc/vtune/performance_analysis_guide.pdf中(但它有关于相关Nehalem-EP变体的更多详细信息).

对于Nehalem(Intel Core i5 750,L3缓存为8MB,没有多CPU /多插槽/ NUMA支持),perf会将标准("通用缓存事件")LLC-load-misses事件映射为..."OFFCORE_RESPONSE.ANY_DATA.ANY_LLC_MISS"在perf事件映射(唯一的)的最佳文档中 - 内核源代码

http://elixir.free-electrons.com/linux/v4.8/source/arch/x86/events/intel/core.c#L1103

 u64 nehalem_hw_cache_event_ids ...
[ C(LL  ) ] = {
    [ C(OP_READ) ] = {
        /* OFFCORE_RESPONSE.ANY_DATA.LOCAL_CACHE */
        [ C(RESULT_ACCESS) ] = 0x01b7,
        /* OFFCORE_RESPONSE.ANY_DATA.ANY_LLC_MISS */
        [ C(RESULT_MISS)   ] = 0x01b7,
...
/*
 * Nehalem/Westmere MSR_OFFCORE_RESPONSE bits;
 * See IA32 SDM Vol 3B 30.6.1.3
 */
#define NHM_DMND_DATA_RD    (1 << 0)
#define NHM_DMND_READ       (NHM_DMND_DATA_RD)
#define NHM_L3_MISS (NHM_NON_DRAM|NHM_LOCAL_DRAM|NHM_REMOTE_DRAM|NHM_REMOTE_CACHE_FWD)
...
 u64 nehalem_hw_cache_extra_regs
  ..
 [ C(LL  ) ] = {
    [ C(OP_READ) ] = {
        [ C(RESULT_ACCESS) ] = NHM_DMND_READ|NHM_L3_ACCESS,
        [ C(RESULT_MISS)   ] = NHM_DMND_READ|NHM_L3_MISS,
Run Code Online (Sandbox Code Playgroud)

我认为这个事件并不精确:cpu管道会将(无序)加载请求发布到缓存层次结构并执行其他指令.经过一段时间(大约10个周期到达并从L2获得响应并且40个周期达到L3)将在相应的(offcore?)PMU中有未命中标志的响应以增加计数器.在此计数器溢出时,将从此PMU生成分析中断.在几个cpu时钟周期中,它将到达管道中断它,perf_events子系统的处理程序将通过注册当前(中断的)EIP/RIP指令指针并将PMU计数器重置为某个负值来处理此问题(例如,-100000以获得每个中断)计算100000 L3未命中;用于perf record -e LLC-load-misses -c 100000设置精确计数或perf将自动调谐限制以获得一些默认频率).注册的EIP/RIP不是加载命令的IP,也可能不是要使用加载数据的命令的EIP/RIP.

但是如果您的CPU是系统中唯一的插槽并且您访问普通内存(而不是某些映射的PCI-express空间),则L3 miss实际上将实现为本地内存访问,并且有一些计数器...(https: //software.intel.com/en-us/node/596851 - "此处丢失的任何内存请求必须由本地或远程DRAM提供服务").

您的CPU有一些PMU事件列表:

应该有一些关于ANY_LLC_MISS offcore PMU事件实现的信息和Nhm的PEBS事件列表,但我现在找不到它.

我可以推荐你使用ocperfhttps://github.com/andikleen/pmu-tools与CPU的任何PMU事件,而不需要手动进行编码.您的CPU中有一些PEBS事件,并且有延迟分析/ perf mem某种内存访问分析(一些随机性的性能pdf:2012年帖子"性能:添加内存访问抽样支持",RH 2013 - 第26-30页,仍然没有记录在2015年 - sowa pg19,ls /sys/devices/cpu/events).对于较新的CPU,有更新的工具,如ucevent.

我也建议你尝试使用GUI 的程序的cachegrindprofiler /缓存模拟器工具来查看配置文件.基于Valgrind的分析器可以帮助您了解代码的工作原理:它们为每条指令收集精确的指令执行计数,而cachegrind也模拟一些抽象的多级缓存.但是,真正的CPU将每个循环执行多个指令(以便,/ 1个指令= 1级的CPU时钟周期的成本模型给出了一些错误; cachegrind缓存模型具有不相同的逻辑实缓存).并且所有工具都是动态二进制检测工具,与本机运行相比,它会使程序速度降低20-30倍.valgrindkcachegrindcallgrindcachegrindvalgrind