【问题标题】:perf annotated assembly seems offperf 带注释的程序集似乎已关闭
【发布时间】:2015-04-09 02:08:08
【问题描述】:

我想测量 C++ 原子 fetch_add 在不同设置上所花费的时间。我写了这样的东西:

atomic<uint64_t> x(0);
for (uint64_t i = 0; i < REPS; i+=1g) {
  x.fetch_add(1);
} 

所以如果REPS 足够高,我假设将能够平均每秒发生的fetch_add。首先,我需要验证大部分时间确实花在了 fetch_add 中,而不是循环开销。所以我跑了 perf 来做到这一点。

这是来自 objdump 的程序集:

400ed0:       b8 00 b4 c4 04          mov    $0x4c4b400,%eax
400ed5:       0f 1f 00                nopl   (%rax)
400ed8:       f0 83 05 7c 22 20 00    lock addl $0x1,0x20227c(%rip)
400edf:       01 
400ee0:       83 e8 01                sub    $0x1,%eax
400ee3:       75 f3                   jne    400ed8 <_Z10incrsharedv+0x8>

perf(用于循环事件)表示 100% 的循环进入 sub $0x1,%eax,而不是我所期望的 lock addl $0x1,0x20227c(%rip) 或跳转。任何想法为什么?这是准确的,还是只是一个测量工件?在第二种情况下,为什么 perf 会系统地将延迟归因于 sub 行而不是 addl

【问题讨论】:

  • 请一次一个问题?
  • 好建议。我已经删除了次要问题。

标签: c++ c++11 x86 perf


【解决方案1】:

TL;DR:尝试使用:pp 后缀,对于某些事件,处理器可以帮助您提供更准确的注释数据。

加长版:

在尝试调查我描述的行为时,我还尝试使用以下更展开的循环。我认为它在一定程度上解决了这个问题。

  for (uint64_t i = 0; i < REPS; i+=10) {

    x.fetch_add(1, ORDER);
    x.fetch_add(1, ORDER);
    x.fetch_add(1, ORDER);
    x.fetch_add(1, ORDER);
    x.fetch_add(1, ORDER);

    x.fetch_add(1, ORDER);
    x.fetch_add(1, ORDER);
    x.fetch_add(1, ORDER);
    x.fetch_add(1, ORDER);
    x.fetch_add(1, ORDER);
  }

当使用perf record -e cycles

生成的 perf 注释是:

      :      0000000000400f00 <incr(std::atomic<unsigned long>&)>:
 0.00 :        400f00:       mov    $0x3d0900,%eax
 0.00 :        400f05:       nopl   (%rax)
 0.00 :        400f08:       lock addq $0x1,(%rdi)
10.93 :        400f0d:       lock addq $0x1,(%rdi)
 9.77 :        400f12:       lock addq $0x1,(%rdi)
10.22 :        400f17:       lock addq $0x1,(%rdi)
 8.97 :        400f1c:       lock addq $0x1,(%rdi)
10.39 :        400f21:       lock addq $0x1,(%rdi)
 9.87 :        400f26:       lock addq $0x1,(%rdi)
10.48 :        400f2b:       lock addq $0x1,(%rdi)
 9.70 :        400f30:       lock addq $0x1,(%rdi)
10.19 :        400f35:       lock addq $0x1,(%rdi)
 9.49 :        400f3a:       sub    $0x1,%rax
 0.00 :        400f3e:       jne    

当我将 fetch add 的调用次数更改为 5 时,识别出 5 个热点。这个结果表明在这种情况下,在归因周期时存在系统性的非一指令错误:

perf wiki 包括以下warning

“基于中断的采样在现代处理器上引入了打滑。这意味着存储在每个采样中的指令指针指定了程序被中断以处理 PMU 中断的位置,而不是计数器实际溢出的位置”

“如果有分支,这两点之间的距离可能是几十条指令或更多。”

所以,看起来我应该认为自己很幸运,因为注释被取消了 ;)。

更新:英特尔处理器支持称为 PEBS(基于精确事件的采样)的功能,这使得将指令指针与计数器事件关联起来更不容易出错See this forum post

对于选定的计数器,您也可以通过perf访问此功能:

使用 perf record -e cycles:pp 代替(注意 :pp 后缀)这次 annotate 的输出是:

      :      0000000000400f00 <incr(std::atomic<unsigned long>&)>:
 0.00 :        400f00:       mov    $0x3d0900,%eax
 0.00 :        400f05:       nopl   (%rax)
10.75 :        400f08:       lock addq $0x1,(%rdi)
10.15 :        400f0d:       lock addq $0x1,(%rdi)
10.00 :        400f12:       lock addq $0x1,(%rdi)
 9.22 :        400f17:       lock addq $0x1,(%rdi)
10.21 :        400f1c:       lock addq $0x1,(%rdi)
 9.75 :        400f21:       lock addq $0x1,(%rdi)
 9.95 :        400f26:       lock addq $0x1,(%rdi)
10.02 :        400f2b:       lock addq $0x1,(%rdi)
10.18 :        400f30:       lock addq $0x1,(%rdi)
 9.75 :        400f35:       lock addq $0x1,(%rdi)
 0.00 :        400f3a:       sub    $0x1,%rax
 0.00 :        400f3e:       jne    400f08 

这证实了预感。这是一个解决方案,在跳跃的更棘手的情况下可能会有所帮助。

【讨论】:

  • 这是有道理的。 perf 通过检查定时器中断上的指令指针来工作。中断在触发它的指令之后处理,因此如果 fetch_add 占用大部分时间,则指令指针很可能在定时器处理程序中紧随其后。
  • 有趣。你有一个指向哪里可以了解更多关于性能内部的指针吗?
  • 我的评论只是基于对处理器如何处理中断的了解,但perf wiki 提到它实际上可能更远,并且有很多其他信息。
猜你喜欢
  • 2012-04-30
  • 2012-08-12
  • 1970-01-01
  • 2016-09-18
  • 1970-01-01
  • 1970-01-01
  • 2014-07-09
  • 2023-02-18
  • 1970-01-01
相关资源
最近更新 更多