perf 带注释的程序集似乎已关闭

orm*_*orm 5 c++ x86 c++11 perf

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

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

因此,如果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>
Run Code Online (Sandbox Code Playgroud)

perf (对于周期事件)表示 100% 的周期进入,这与我所期望的或跳跃sub $0x1,%eax相反。lock addl $0x1,0x20227c(%rip)有什么想法吗?这是准确的,还是只是一个测量工件?在第二种情况下,为什么 perf 会系统地将延迟归因于线路sub而不是addl

orm*_*orm 7

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);
  }
Run Code Online (Sandbox Code Playgroud)

使用时perf record -e cycles

生成的性能注释是:

      :      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    
Run Code Online (Sandbox Code Playgroud)

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

perf wiki 包含以下警告

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

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

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

更新: 英特尔处理器支持称为 PEBS(基于精确事件的采样)的功能,该功能使得将指令指针与计数器事件关联起来更不容易出错, 请参阅此论坛帖子

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 
Run Code Online (Sandbox Code Playgroud)

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