我希望能够手动预测任意算术运算(即没有分支或内存,尽管这也很好),但考虑到指令重新排序,x86-64汇编代码在给定的特定架构下将采用多长时间,超标量,延迟,CPI等。
要实现此目标,必须遵循什么/描述规则?
我认为我已经弄清了一些初步规则,但是在将任何示例代码分解到如此详细的级别时,我还找不到任何参考,因此我不得不做一些猜测。 (例如,英特尔优化手册几乎没有对提及指令进行重新排序。)
至少,我要寻找(1)确认每条规则正确还是对每条规则的正确陈述,以及(2)我可能忘记的任何规则的列表。
addps
,每个周期它可以接受1条新指令,总功能单元的数量为1 / CPI。和subps
使用相同的功能单元?如何确定?并且:4
)的指令。作为示例,请考虑以下示例代码(用于计算叉积):
shufps xmm3, xmm2, 210
shufps xmm0, xmm1, 201
shufps xmm2, xmm2, 201
mulps xmm0, xmm3
shufps xmm1, xmm1, 210
mulps xmm1, xmm2
subps xmm0, xmm1
我为Haswell预测延迟的尝试看起来像这样:
; `mulps` Haswell latency=5, CPI=0.5
; `shufps` Haswell latency=1, CPI=1
; `subps` Haswell latency=3, CPI=1
shufps xmm3, xmm2, 210 ; cycle 1
shufps xmm0, xmm1, 201 ; cycle 2
shufps xmm2, xmm2, 201 ; cycle 3
mulps xmm0, xmm3 ; (superscalar execution)
shufps xmm1, xmm1, 210 ; cycle 4
mulps xmm1, xmm2 ; cycle 5
; cycle 6 (stall `xmm0` and `xmm1`)
; cycle 7 (stall `xmm1`)
; cycle 8 (stall `xmm1`)
subps xmm0, xmm1 ; cycle 9
; cycle 10 (stall `xmm0`)
答案 0 :(得分:6)
相关:How many CPU cycles are needed for each assembly instruction?是对每条指令的吞吐量与延迟之间关系的良好介绍,以及这对多条指令序列的含义。
这称为静态(性能)分析。维基百科(https://en.wikipedia.org/wiki/List_of_performance_analysis_tools)说,AMD的AMD CodeXL具有“静态内核分析器”(即用于计算内核,又名循环)。我从未尝试过。
Intel还有一个免费的工具,用于分析在Sandybridge系列CPU中循环如何通过管道: What is IACA and how do I use it?
IACA还不错,但是有错误(例如,Sandybridge上shld
的数据错误,最后我检查了一下,它不知道Haswell/Skylake can keep indexed addressing modes micro-fused for some instructions。但是随着英特尔的加入,也许情况会有所改变) IACA也无助于计数前端uops以查看您有多接近瓶颈(它只给您未融合域的uop计数)。
静态分析通常非常好,但是绝对可以通过性能计数器进行分析来检查。请参见Can x86's MOV really be "free"? Why can't I reproduce this at all?,了解一个配置简单循环以研究微体系结构特征的示例。
Agner Fog's微体系结构指南(第2章:乱序执行)介绍了依赖关系链和乱序执行的一些基础知识。他的“优化装配体”指南包含更多入门和高级性能方面的内容。
他的微体系结构指南的后续章节介绍了Nehalem,Sandybridge,Haswell,K8 / K10,Bulldozer和Ryzen等CPU中管线的详细信息。 (还有Atom / Silvermont / Jaguar)。
Agner Fog的指令表(电子表格或PDF)通常也是指令延迟/吞吐量/执行端口故障的最佳来源。
David Kanter的微结构分析文档非常好,带有图表。例如https://www.realworldtech.com/sandy-bridge/,https://www.realworldtech.com/haswell-cpu/和https://www.realworldtech.com/bulldozer/。
另请参见the x86 tag wiki中的其他效果链接。
我也试图解释CPU内核如何在this answer中发现和利用指令级并行性,但是我认为您已经掌握了与调优软件有关的那些基础知识。我确实提到过SMT(超线程)是如何将更多ILP暴露给单个CPU内核的。
以Intel术语:
“问题” 意味着将uop发送到核心的乱序部分;连同寄存器重命名,这是前端的最后一步。问题/重命名阶段通常是管道中最狭窄的部分,例如从Core2开始在Intel上扩展4倍。 (由于SKL改进的解码器和uop-cache带宽,以及后端和缓存带宽的改进,像Haswell尤其是Skylake之类的后来的uarch通常实际上在某些实际代码中非常接近。)这是融合域uops :micro-fusion可让您通过前端发送2 uops,并且仅占用一个ROB条目。 (我能够在sustains 7 unfused-domain uops per clock的Skylake上构建一个循环)。另请参阅http://blog.stuffedcow.net/2013/05/measuring-rob-capacity/ re:乱序窗口大小。
“调度” 表示调度程序将uop发送到执行端口。一旦所有输入准备就绪,并且相关的执行端口可用,就会发生这种情况。 How are x86 uops scheduled, exactly?。调度发生在“未融合”域中;在OoO调度程序(又称为预留站,RS)中分别跟踪微融合的微码。
许多其他计算机体系结构文献也使用相反的术语,但这是您可以在英特尔优化手册中找到的术语,以及诸如uops_issued.any
或{{1}之类的硬件性能计数器的名称。 }。
任意算术x86-64汇编代码需要多长时间
您的最终uops_dispatched_port.port_5
结果不必在CPU开始运行以后的指令之前就已准备就绪。延迟仅对以后需要该值作为输入的指令起作用,而与整数循环无关紧要。
有时候吞吐量很重要,乱序的exec可以隐藏多个独立的短依赖链的延迟。 (例如,如果您对包含多个向量的大型数组的每个元素执行相同的操作,则可以一次飞行多个叉积。)即使按程序顺序,您也将一次飞行多个迭代。您需要先完成一次迭代,然后再进行下一次迭代。 (如果OoO执行人员很难在硬件中进行所有重新排序,则软件流水线可以为高延迟循环体提供帮助。)
根据这三个因素,您可以大致表征一小段非分支代码。通常,其中只有一个是给定用例的瓶颈。通常,您正在查看要用作循环的 part 的块,而不是整个循环的主体,但是 OoO exec通常工作得很好,您可以将这些数字加起来如果不是很长一段时间,以至于OoO窗口大小会阻止找到所有ILP。
uop计数(未融合的域)。例如乱码的代码通常会在Intel CPU的端口5上出现瓶颈。英特尔通常只发布吞吐量数字,而不发布端口故障,这就是为什么如果您不只是重复同一条指令数百万次,您就必须查看Agner Fog的表(或IACA输出)来做有意义的事情。
通常,您可以假定最佳情况下的调度/分发,因为可以在其他端口上运行的uops不会经常窃取繁忙的端口,但是确实会发生这种情况。 (How are x86 uops scheduled, exactly?)
仅依靠CPI ;两条CPI = 1指令可能会竞争也可能不会竞争相同执行端口。如果没有,则可以并行执行。例如Haswell只能在端口0上运行subps
(5c延迟,1c吞吐量,即CPI = 1),但是它是单个uop,因此1 psadbw
+ 3 psadbw
条指令的混合可以维持4每个时钟指令。在Intel CPU的3个不同端口上都有矢量ALU,其中一些操作在所有3个端口(例如布尔值)上复制,而某些操作仅在一个端口上复制(例如Skylake之前的移位)。
有时候,您可以提出几种不同的策略,一种可能会降低延迟,但会花费更多的时间。一个典型的例子是multiplying by constants,例如add
(Intel上为1 uop,3c延迟)与imul eax, ecx, 10
/ lea eax, [rcx + rcx*4]
(2 oups,2c延迟)之比。现代编译器倾向于选择2 LEA而不是1 IMUL,尽管直到3.7的clang都赞成IMUL,除非它只能用一条其他指令完成工作。
有关用于实现函数的几种不同方式的静态分析示例,请参见What is the efficient way to count set bits at a position or lower?。
另请参阅Why does mulss take only 3 cycles on Haswell, different from Agner's instruction tables?(最终比您从问题标题中猜到的要详细得多),以获取静态分析的另一摘要,以及有关使用多个累加器进行展开以减少费用的一些精巧内容。
每个(?)功能单元都已流水线化
分频器在最近的CPU中流水线化,但不是完全流水线化。 (但是,FP分频是单码组,因此,如果您将一个add eax,eax
与数十个divps
/ mulps
混合在一起,则即使延迟无关紧要,它对吞吐量的影响也可以忽略不计: Floating point division vs floating point multiplication。addps
+牛顿迭代会降低吞吐量,并且延迟大约相同。
所有其他内容都已在主流英特尔CPU上全面流水线化;单个uop的多周期(互惠)吞吐量。 (像rcpps
这样的可变计数整数移位在3 uop时的吞吐量低于预期,因为它们通过标志合并的uops产生了依赖性。但是,如果通过{{1} }之类的,您可以获得better throughput and latency。)
在Ryzen之前的AMD上,整数乘数也仅部分流水线。例如推土机的shl eax, cl
仅1 uop,但延迟为4c,吞吐量为2c。
至强融核(KNL)也有一些未完全流水线的洗牌指令,但它往往会在前端(指令解码)而不是后端造成瓶颈,并且确实具有较小的缓冲区+ OoO exec功能隐藏后端气泡。
如果是浮点指令,则在它发出之前的每个浮点指令(浮点指令都会对静态指令进行重新排序)
否。
也许您读过Silvermont,它对FP / SIMD不执行OoO exec,而只能是整数(带有20 uop的小窗口)。也许某些ARM芯片也是如此,但NEON的调度程序更简单?我对ARM uarch详细信息了解不多。
主流的大核心微体系结构(如P6 / SnB系列)以及所有AMD OoO芯片,对SIMD和FP指令的OoO执行与对整数的相同。 AMD CPU使用单独的调度程序,但是Intel使用统一的调度程序,因此可以将其完整大小应用于以整数或FP代码查找ILP(无论当前正在运行的是哪个)。
甚至位于Silvermont的Knight's Landing(在Xeon Phi)也为SIMD做OoO主管。
x86通常对指令排序不太敏感,但是uop调度不进行关键路径分析。因此,有时可能有助于先将指令放在关键路径上,这样当其他指令在该端口上运行时,它们就不会卡在等待输入准备就绪的情况下,导致稍后我们遇到需要指令结果的指令时停滞更大。关键路径。 (也就是说,这就是关键路径。)
我为Haswell预测延迟的尝试看起来像这样:
是的,看起来不错。 add
在端口5上运行,imul ecx, edx
在p1上运行,shufps
在p0或p1上运行。 Skylake丢弃专用的FP-add单元,并在p0 / p1的FMA单元上运行SIMD FP add / mul / FMA,所有这些都具有4c的延迟(从Haswell中的3/5/5或从3/3/5中的上/下) Broadwell)。
这是一个很好的例子,说明为什么通常在SIMD向量中保留整个XYZ方向向量很烂。保留X数组,Y数组和Z数组会让您并行执行4个交叉产品,没有任何洗牌。
SSE tag wiki包含以下幻灯片的链接:SIMD at Insomniac Games (GDC 2015)涵盖了3D向量的结构数组与数组结构问题,以及为什么总是尝试通常是错误的只需执行一次SIMD操作即可,而不是使用SIMD并行执行多项操作。