bry*_*rto 2 x86 assembly pointers cpu-architecture perf
我有一个执行指针追踪的程序,我正在尝试尽可能地优化指针追踪循环。我注意到perf record
检测到函数中大约 20% 的执行时间myFunction()
用于执行跳转指令(用于在读取特定值后退出循环)。
需要注意的一些事项:
__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
...
Run Code Online (Sandbox Code Playgroud)
我希望在这个循环中花费的大部分周期都用于从内存中读取值,这是由 perf 确认的。我还希望剩余的周期在执行循环中剩余的指令时会有些均匀地花费。相反, perf 报告的是剩余周期的很大一部分用于执行跳转。
我怀疑通过了解用于执行这些指令的微操作,我可以更好地了解这些成本,但我对从哪里开始有点迷茫。
请记住cycles
,即使mov
-load 和宏融合的cmp
-and-branch uops 都在等待结果,事件也必须选择要归咎于的指令。这不是运行时的一个或另一个“成本计算周期”的问题;他们都在并行等待。(现代微处理器 90 分钟指南!和https://agner.org/optimize/)
但是当“周期”事件计数器溢出时,它必须选择一个特定的指令来“责备”,因为您使用的是统计采样。在这种情况下,一个有数百个 uops 的 CPU 必须发明一张不准确的现实图片。通常是等待缓慢输入的人受到指责,我认为因为它通常是 ROB 或 RS 中最古老的并且阻止前端分配新的 uops。
确切地选择哪条指令的细节可能会告诉我们一些关于 CPU 内部的信息,但只是非常间接的。可能与它如何退出 4(?) uop 组有关,并且此循环有 3 个,因此在发生 perf 事件异常时哪个 uop 最旧。
出于某种原因,4:1 拆分可能很重要,也许是因为 4+1 = 5 周期延迟,加载具有非简单寻址模式。(我假设这是一个 Intel Sandybridge 系列 CPU,也许是 Skylake 派生的?)就像也许如果数据在与 perf 事件溢出(并选择采样)的同一周期从缓存到达,mov
则不会受到指责,因为它真的可以执行并让开吗?
IIRC、BeeOnRope 或其他人通过实验发现,Skylake CPU 倾向于让最旧的未退休指令在异常到达后退出,至少在它不是缓存未命中的情况下。在您的情况下,这将是cmp/jne
循环底部的 ,它按程序顺序出现在下一次迭代顶部的加载之前。