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