给定一个由真正依赖项链接并周期性重复的指令链(即循环),例如(a->b->c)->(a->b->c)->。。。
假设它可以拆分为几个较短且独立的子依赖链,以从无序执行中获益:
- (a0->b0->c0)->(a0->b0->c 0)->
- (a1->b1->c1)->(a1->b1->c1)->
无序引擎将每个指令调度到具有延迟和互易吞吐量的相应CPU单元。
最大化执行吞吐量的子依赖链的最佳数量是多少
根据Agner的汇编语言优化子程序手册,第12.15节:"如果CPU没有其他事情可做,累加器的最佳数量是依赖链中最关键指令的延迟除以该指令的倒数吞吐量"。"最关键指令"是什么意思?是否有其他技术文档可以解决此类问题?
这取决于它们的长度,以及每个周期可以单独运行多少uop。
它还取决于硬件的宽度。例如
- PIII具有两个ALU执行单元和每时钟3个融合域uop吞吐量与
- Haswell有四个ALU执行单元(其中只有三个可以处理向量),每个时钟有4个融合域uop吞吐量
我认为"最关键的指令"是指占关键路径大部分长度的指令。如果一个循环携带的依赖链是由多个具有不同延迟的指令组成的,那么这是某种平均值。(比如几何平均?)
一个很好的例子是FP加法(例如对数组求和):
在Sandybridge上,它每时钟有一个吞吐量,但延迟为3c,因此一条依赖的addps
指令链以每3c一个uop的速度运行,仅维持最大FP乘法吞吐量的1/3。(并使其他两个执行端口完全空闲。)
三条并行的dep链可以使port1充满addps
指令。所以,如果你使用三个累加器,你可以保持三个加法运算。如果您还保持5个FP乘法在飞行中,您也可以饱和端口0。循环开销可以在端口5上运行(希望不会从p01中窃取循环)。负载uop可以通过加法进行微融合,因此不会占用融合域带宽。但是,您可以使用单独的movaps
指令进行一些加载,仍然不会使每个时钟4个融合域的uop吞吐量饱和,但前端的瓶颈可能会限制您的吞吐量。
Haswell对于FP-add仍然只有一个每时钟吞吐量,但是对于FP-mul和FMA每个时钟有两个吞吐量。
因此,如果使用FMA(乘数为1.0)对数组求和,则需要10个矢量累加器(10个dep链)来保持10个FMA的飞行,使p01饱和。p5和p6未被使用,但您也可以使用微融合负载使负载端口饱和。
Skylake将FMA的延迟减少了1个周期,降至4,并放弃了FP添加单元。(因此,FP添加是在FMA单元中完成的,这使[v]addps
的吞吐量增加了一倍,代价是增加了1c的延迟)。
所以在SKL上,你只需要8个向量累加器(8个dep链)就可以饱和p01。但拥有更多的dep链并没有坏处,只要你没有用完寄存器。因此,在Haswell上使用10个累加器的理想代码在SKL上应该仍然是理想的。不过,您可以通过使用addps
而不是常数向量为1.0的fma213ps
(或其他)来节省一些功率。
有关吞吐量/延迟/端口号,请参阅Agner的说明表,有关更多详细信息,请参阅他的微阵列PDF。我没有检查端口号或延迟号,但我经常输入这个例子,我确信它是正确的:P。
另请参阅x86标记wiki中的其他链接。