为什么在执行指针追逐时这条跳转指令如此昂贵?

时间:2021-01-13 17:46:33

标签: pointers assembly x86 cpu-architecture perf

我有一个执行 pointer chasing 的程序,我正在尝试尽可能地优化指针追踪循环。 我注意到 perf record 检测到函数 myFunction() 中约 20% 的执行时间用于执行跳转指令(用于在读取特定值后退出循环)。

注意事项:

  • 指针追踪路径可以轻松放入 L1 数据缓存
  • 使用 __builtin_expect 来避免分支错误预测的成本没有明显效果

perf record 具有以下输出:

Samples: 153K of event 'cycles', 10000 Hz, Event count (approx.): 35559166926                                                                                                                                                               
myFunction  /tmp/foobar [Percent: local hits]                                                                                                                                                                            
Percent│      endbr64                                                                                                                                                                                                                       
      ...
 80.09 │20:   mov     (%rdx,%rbx,1),%ebx                                                                                                                                                                                                    
  0.07 │      add     $0x1,%rax                                                                                                                                                                                                             
       │      cmp     $0xffffffff,%ebx                                                                                                                                                                                                      
 19.84 │    ↑ jne     20                                                                                                                                                                                                                    
      ...

我希望在这个循环中花费的大部分周期都用于从内存中读取值,这是由 perf 确认的。 我还希望剩余的周期在执行循环中剩余的指令时会有些均匀地花费。相反, perf 报告的是剩余周期的很大一部分用于执行跳转。

我怀疑通过了解用于执行这些指令的微操作,我可以更好地了解这些成本,但我有点不知从何开始。

1 个答案:

答案 0 :(得分:5)

请记住,cycles 事件必须选择要归咎的指令,即使 mov-load 和宏融合的 cmp-and-branch uops 都在等待结果.这不是运行时的一个或另一个“成本计算周期”的问题; 他们都在并行等待。 (Modern Microprocessors A 90-Minute Guide!https://agner.org/optimize/

但是当“cycles”事件计数器溢出时,它必须选择一个特定的指令来“责备”,因为您使用的是统计采样。在这种情况下,一个有数百个 uops 的 CPU 必须发明一张不准确的现实图片。通常是等待缓慢输入的人受到指责,我认为是因为它通常是 ROB 或 RS 中最老的并且阻止前端分配新的 uop。

具体选择哪条指令的细节可能会告诉我们一些关于 CPU 内部的信息,但只是非常间接的。可能与它如何退出 4(?) uop 组有关,并且此循环有 3 个,因此在发生 perf 事件异常时哪个 uop 最旧。

出于某种原因,4:1 拆分可能很重要,也许是因为 4+1 = 5 周期延迟,加载具有非简单寻址模式。 (我假设这是一个 Intel Sandybridge 系列 CPU,也许是 Skylake 派生的?)就像也许如果数据在与 perf 事件溢出(并选择采样)的同一周期从缓存到达,mov 不会受到指责是因为它实际上可以执行并让开?

IIRC、BeeOnRope 或其他人通过实验发现,Skylake CPU 倾向于让最旧的未退休指令在异常到达后退出,至少在它不是缓存未命中的情况下。在您的情况下,这将是循环底部的 cmp/jne,它按程序顺序出现在下一次迭代顶部的加载之前。