我认为我对延迟和吞吐量之间的差异有一个很好的理解。但是,对于Intel Intrinsics来说,延迟对指令吞吐量的影响并不清楚,特别是在顺序(或接近顺序)使用多个内部调用时。
例如,让我们考虑一下:
_mm_cmpestrc
Haswell处理器的延迟为11,吞吐量为7。如果我在一个循环中运行这个指令,我会在11个循环后得到一个连续的每循环输出吗?因为这需要一次运行11条指令,并且因为我的吞吐量为7,所以我是否会用完“执行单元”?
我不确定如何使用延迟和吞吐量,而不是了解单个指令相对于不同版本的代码需要多长时间。
答案 0 :(得分:9)
有关CPU性能的更完整信息,请参阅Agner Fog's microarchitecture guide and instruction tables。 (他的优化C ++和优化装配指南也非常出色)。另请参阅x86标记wiki中的其他链接,尤其是英特尔的优化手册。
有关分析短序列代码的示例,请参阅
单个指令的延迟和吞吐量实际上不足以为使用混合向量指令的循环获得有用的图片。这些数字并不能告诉您哪些内在函数(asm指令)相互竞争吞吐量资源(即它们是否需要相同的执行端口)。它们仅适用于超简单循环,例如加载/做一件事/商店,或者例如将数组与_mm_add_ps
或_mm_add_epi32
相加。
您可以使用多个累加器来获得更多instruction-level parallelism,但您仍然只使用一个内在函数,因此您有足够的信息来查看,例如Skylake之前的CPU只能维持每个时钟一_mm_add_ps
的吞吐量,而SKL每个时钟周期可以启动两个(每0.5c一个的倒数吞吐量)。它可以在其完全流水线化的FMA执行单元上运行ADDPS,而不是只有一个专用的FP-add单元,因此吞吐量更高但延迟时间比Haswell(3c lat,每1c输出一个)更差。
由于_mm_add_ps
在Skylake上有4个周期的延迟,这意味着8个vector-FP添加操作可以同时进行。因此,您需要8个独立的向量累加器(最后将它们相互添加)以暴露出那么多的并行性。 (例如,使用8个单独的__m256 sum0, sum1, ...
变量手动展开循环。编译器驱动的展开(使用-funroll-loops -ffast-math
编译)通常会使用相同的寄存器,但循环开销并不是问题所在。/ p>
这些数字也忽略了英特尔CPU性能的第三个主要方面:融合域uop吞吐量。大多数指令解码为单个uop,但有些指令解码为多个uop。 (特别是SSE4.2字符串指令,如你提到的_mm_cmpestrc
:PCMPESTRI在Skylake上是8 uops)。即使在任何特定的执行端口上没有瓶颈,你仍然可以阻止前端保持无序核心工作的能力。英特尔Sandybridge系列CPU每个时钟最多可以发出4个融合域uop,实际上通常可以接近其他瓶颈不会发生的情况。 (有关不同循环大小的一些有趣的最佳案例前端吞吐量测试,请参阅Is performance reduced when executing loops whose uop count is not a multiple of processor width?。)由于加载/存储指令使用与ALU指令不同的执行端口,因此当L1缓存中的数据很热时,这可能成为瓶颈。
除非您查看编译器生成的asm,否则您不知道编译器必须使用多少额外的MOVDQA指令来在寄存器之间复制数据,以解决没有AVX的大多数指令替换它们的事实第一个源注册结果。 (即破坏性的目的地)。您还不知道循环中任何标量操作的循环开销。
我认为我对延迟和吞吐量之间的区别有一个很好的理解
你的猜测似乎没有意义,所以你肯定错过了什么。
CPUs are pipelined,其中的执行单位也是如此。 A"完全流水线"执行单元可以在每个周期开始一个新操作(吞吐量=每个时钟一个)
(倒数)吞吐量是当没有数据依赖性迫使它等待时,操作可以开始的频率,例如,该指令每7个周期一个。
延迟是指一个操作的结果准备好所需的时间,并且通常仅在它是循环携带的依赖链的一部分时才有意义。
如果循环的下一次迭代独立于前一次运行,则无序执行可以"参见"足够远,可以在两次迭代之间找到instruction-level parallelism并保持忙碌,仅限于吞吐量。
(未完全完成编辑,稍后会对其进行整理。)