【发布时间】:2023-12-17 01:45:02
【问题描述】:
我已经反汇编了一个使用 MSVC v140 编译的小型 C++ 程序,并试图估计每条指令的周期,以便更好地了解代码设计如何影响性能。我一直在关注 Mike Acton 在 "Data-Oriented Design and C++" 上的 CppCon 2014 演讲,特别是我链接到的部分。
在其中,他指出了以下几行:
movss 8(%rbx), %xmm1
movss 12(%rbx), %xmm0
然后他声称这些 2 x 32 位读取可能在同一高速缓存行上,因此大约需要大约 200 个周期。
Intel 64 and IA-32 Architectures Optimization Reference Manual 是一个很好的资源,特别是“附录 C - 指令延迟和吞吐量”。然而,在 “表 C-16. 流式 SIMD 扩展单精度浮点指令”中的 C-15 页上,它声明 movss 只有 1 个周期(除非我我理解这里的延迟意味着什么是错误的......如果是这样,我该如何阅读这个东西?)
我知道theoretical prediction of execution time 永远不会是正确的,但学习这一点很重要。 这两个命令的 200 个周期如何,我如何学会推理超出这个 sn-p 的执行时间?
我已经开始阅读有关 CPU 流水线的一些内容......也许大部分周期都在那里被拾取?
PS:我对在这里实际测量硬件性能计数器不感兴趣。我只是想学习如何合理地阅读 ASM 和周期。
【问题讨论】:
-
你看过 Agner Fog 的作品吗? agner.org/optimize/instruction_tables.pdf
-
你不能再计算周期了。现在很久没有了。流水线、缓存、分支预测等……流水线就像工厂里的流水线一样。你可能有 117 个步骤或工位来建造一件东西,每个可能需要 30 秒,但这意味着理论上你可以每 30 秒生产一件产品,而不是因为生产线而每小时生产一件。这就是他们从后端出来的速度。
-
大多数(如果不是所有)指令都需要一个时钟来执行,它们可能需要 15 到 1000 个时钟来通过缓存未命中获取相对较慢的 DRAM,并且所有其他步骤都在管道中,但是一旦所有输入存在于执行单元中,则流水线中的这一步骤需要一个时钟。然后是接下来的所有内容,将结果保存在寄存器或慢速内存等中。
-
不要误会我的意思,你所做的事情有很多价值,了解编译器正在产生什么,如何操纵编译器来产生不同的东西。明白指令的数量不等于速度。内存指令比寄存器指令花费更长的时间,同样,如果你有缓存未命中,你可以有几十个寄存器指令运行一个基于内存的指令,所有其他的东西都保持不变。使用多个执行单元、管道,您可以重新排列不必按特定顺序排列的指令并改变整体性能
-
和其他有趣的事情,例如添加一个或多个 nop 或删除它们以更改整个程序或部分程序的缓存对齐方式,并查看性能变化。
标签: performance assembly architecture x86 sse