加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 综合聚焦 > 服务器 > Linux > 正文

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

发布时间:2020-12-14 00:54:14 所属栏目:Linux 来源:网络整理
导读:我正在尝试将一些性能工程技术应用于Dijkstra算法的实现.为了找到(天真的和未经优化的)程序中的瓶颈,我使用perf命令来记录缓存未命中的数量.相关的代码片段如下,它找到距离最小的未访问节点: for (int i = 0; i count; i++) { if (!visited[i]) { if (tmp =
我正在尝试将一些性能工程技术应用于Dijkstra算法的实现.为了找到(天真的和未经优化的)程序中的瓶颈,我使用perf命令来记录缓存未命中的数量.相关的代码片段如下,它找到距离最小的未访问节点:

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

对于LLC-load-misses指标,perf报告显示程序集的以下注释:

│             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,%xmm0                                                                                                                                        ?
 57.99 │     ↑ jbe    ff                                                                                                                                                         ?
       │                         tmp = i;                                                                                                                                        ?
       │       mov    %eax,%r13d                                                                                                                                                 ?
       │     ↑ jmp    ff                                                                                                                                                         ?
       │                     }                                                                                                                                                   ?
       │                 }                                                                                                                                                       ?
       │             }

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

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

解决方法

关于你的例子:

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

│       movsd  (%rcx,%xmm0
   0.13 │       ucomis (%rcx,%xmm0
  57.99 │     ↑ jbe    ff

“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]”生成两次内存访问,并且这两个值都在ucomis指令中使用,该指令与jbe条件跳转(部分?)融合. dist [i]或dist [tmp]或两个表达式都会产生大量未命中.任何这样的未命中将阻止ucomis生成结果并阻塞jbe以给出下一条指令以执行(或退出预测的指令).因此,jbe可能会获得高计数器的所有声誉,而不是真正的内存访问指令(对于像“缓存”响应这样的“远”事件,最后一个被阻止的指令存在一些偏差). 您可以尝试将visited [N]和dist [N]数组合并到struct {int visited;的数组[N]中.当你访问array [i] .visited时,浮动dist}强制预取array [i] .dist,或者你可以尝试改变顶点访问的顺序,或重新编号图顶点,或为下一个或多个元素做一些软件预取( ?) 关于名称问题和可能的非核心倾斜的通用perf事件. Linux中的perf(perf_events)工具在被称为perf list时使用预定义的事件集,并且某些列出的硬件事件可能无法实现;其他映射到当前的CPU功能(并且一些映射不完全正确).有关真实PMU的一些基本信息在您的https://software.intel.com/sites/products/collateral/hpc/vtune/performance_analysis_guide.pdf中(但它有相关Nehalem-EP变体的更多详细信息).

对于Nehalem(Intel Core i5 750,L3缓存为8MB,没有多CPU /多插槽/ NUMA支持),perf会将标准(“Generic cache events”)LLC-load-miss事件映射为..“OFFCORE_RESPONSE.ANY_DATA.ANY_LLC_MISS”as写在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,

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

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

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

>英特尔官方“英特尔?64和IA-32架构软件开发人员手册”(SDM):https://software.intel.com/en-us/articles/intel-sdm,第3卷,附录A

> 3B:https://software.intel.com/sites/default/files/managed/7c/f1/253669-sdm-vol-3b.pdf“18.8基于英特尔?微观结构代码名称NEHALEM的处理器性能监测”,第213页“第3B卷18-35”
> 3B:https://software.intel.com/sites/default/files/managed/7c/f1/253669-sdm-vol-3b.pdf“19.8 – 基于英特尔?微体系结构代码名称Nehalem的处理器”,第365页和“Vol.3B 19-61”)
> Offcore响应编码的其他一些卷?卷. 3A 18-26?

>来自oprofile http://oprofile.sourceforge.net/docs/intel-corei7-events.php
>来自libpfm4的showevtinfo http://www.bnikolic.co.uk/blog/hpc-prof-events.html(注意,这个页面有Sandy Bridge列表,在你的PC上运行libpfm4 ant来获取你的列表). libpfm4中还有check_events工具可帮助您将编码事件作为perf的raw.
>来自VTune文档:http://www.hpc.ut.ee/dokumendid/ips_xe_2015/vtune_amplifier_xe/documentation/en/help/reference/pmw_sp/events/offcore_response.html
>来自Nehalem PMU指南:https://software.intel.com/sites/default/files/m/5/2/c/f/1/30320-Nehalem-PMU-Programming-Guide-Core.pdf
>来自Intel的perf开发人员Andi Kleen的ocperf工具,他的pmu-tools https://github.com/andikleen/pmu-tools的一部分.ocperf只是perf的包装器,这个包将下载事件描述,任何支持的事件名称将被转换为正确的原始编码ofperf`.

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

我建议您使用https://github.com/andikleen/pmu-tools的ocperf和CPU的任何PMU事件,而无需手动编码.您的CPU中有一些PEBS事件,并且存在用于某种内存访问分析的延迟分析/ perf mem(一些随机性执行pdf:2012 post “perf: add memory access sampling support”,RH 2013 – pg26-30,still not documented in 2015 – sowa pg19,ls / sys / devices / cpu / events).对于较新的CPU,有更新的工具,如ucevent.

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

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读