我有一个程序可以执行指针追逐 https://en.wikichip.org/wiki/pointer_chasing我正在尝试尽可能优化指针追逐循环。
我注意到perf record
检测到函数中约 20% 的执行时间myFunction()
用于执行跳转指令(用于在读取特定值后退出循环)。
需要注意的一些事项:
- 指针追踪路径可以轻松地放入 L1 数据缓存中
- using
__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 报告剩余周期的大部分用于执行跳转。
我怀疑通过了解用于执行这些指令的微操作可以更好地理解这些成本,但我有点不知道从哪里开始。
请记住,cycles
事件必须选择一条指令来归咎,即使两者都mov
-加载和宏融合cmp
-and-branch uops 正在等待结果。这不是运行时一个或另一个“成本周期”的问题;而是一个问题。他们都在并行等待. (现代微处理器
90 分钟指南! http://www.lighterra.com/papers/modernmicroprocessors/ and https://agner.org/optimize/ https://agner.org/optimize/)
但是当“cycles”事件计数器溢出时,它必须选择一条特定的指令来“责备”,因为您使用的是统计抽样。这就是具有数百个正在运行的微指令的 CPU 必须发明不准确的现实图景的地方。通常,等待缓慢输入的那个会受到指责,我认为因为它通常是 ROB 或 RS 中最旧的,并且阻止前端分配新的微指令。
到底选择哪条指令的细节可能会告诉我们有关 CPU 内部结构的一些信息,但只是非常间接的。也许与它如何退休 4(?) 个 uop 组有关,而这个循环有 3 个 uop,所以当发生 perf 事件异常时哪个 uop 是最旧的。
出于某种原因,4:1 分割可能很重要,可能是因为采用非简单寻址模式的负载的 4+1 = 5 个周期延迟。 (我假设这是一个 Intel Sandybridge 系列 CPU,也许是 Skylake 衍生的?)就像,如果数据在与 perf 事件溢出(并选择采样)相同的周期从缓存到达,则mov
不会受到指责,因为它实际上可以执行并摆脱困境?
IIRC、BeeOnRope 或其他人通过实验发现 Skylake CPU 倾向于在异常到达后让最旧的未退休指令退休,至少如果不是缓存未命中的话。在你的情况下,那就是cmp/jne
位于循环的底部,按程序顺序出现在下一次迭代顶部的加载之前。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)