perf 注释程序集似乎关闭

perf annotated assembly seems off

我想测量 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 内,而不是循环开销,例如。所以我 运行 执行此操作。

这是来自 objdump 的程序集:

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

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

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

生成的性能注释是:

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

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

perf wiki 包括以下内容warning

"Interrupt-based sampling introduces skids on modern processors. That means that the instruction pointer stored in each sample designates the place where the program was interrupted to process the PMU interrupt, not the place where the counter actually overflows"

"the distance between those two points may be several dozen instructions or more if there were taken branches."

所以,看起来我应该认为自己很幸运,因为注释差了一个 ;)。

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

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

改用 perf record -e cycles:pp(注意 :pp 后缀)这次注释的输出是:

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

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