Agner Fog的优化指南非常出色。他提供了有关所有最新x86 CPU设计(可追溯至Intel Pentium)的微体系结构的指南,指令时序表和文档。另请参阅从/programming//tags/x86/info链接的其他一些资源
只是为了好玩,我会回答一些问题(来自最近的Intel CPU的数字)。选择操作不是优化代码的主要因素(除非您可以避免除法。)
CPU上的单倍乘法是否比加法慢?
是的(除非是2的幂)。(延迟是3-4倍,在Intel上每个时钟吞吐量只有一个。)但是,请尽全力避免它,因为它的速度是2或3一样快。
基本数学和控制流操作码的速度特性到底是什么?
如果您想确切了解:P,请参阅Agner Fog的说明表和微体系结构指南。注意有条件的跳跃。无条件跳转(如函数调用)的开销很小,但不多。
如果两个操作码需要执行相同数量的周期,那么两者可以互换使用而没有任何性能增益/损失?
不,他们可能会与其他竞争相同的执行端口,或者可能不会竞争。这取决于CPU可以并行处理哪些其他依赖项链。(在实践中,通常没有任何有用的决定。偶尔会出现一个向量移位或向量混洗的情况,它们可以在Intel CPU的不同端口上运行。但是整个寄存器的字节移位(PSLLDQ
等)在随机播放单元中运行。)
您可以分享有关x86 CPU性能的任何其他技术细节,
Agner Fog的microarch文档充分详细地描述了Intel和AMD CPU的流水线,以准确计算出每个循环循环应执行多少个周期,以及瓶颈是uop吞吐量,依赖性链还是对一个执行端口的争用。在StackOverflow上查看我的一些答案,例如this或this。
另外, 如果您喜欢CPU设计,则http://www.realworldtech.com/haswell-cpu/(对于早期设计也是如此)很有趣。
这是您的清单,根据我的最佳评价对Haswell CPU进行了排序。除了调整asm循环之外,这实际上不是思考事物的有用方法。缓存/分支预测效果通常占主导地位,因此编写代码要具有良好的模式。数字非常容易动摇,即使吞吐量不成问题,也要设法解决高延迟问题,或者尝试生成更多微指令,从而阻塞管道,使其他事情并行发生。Esp。缓存/分支编号非常虚构。延迟对于循环承载的依赖关系很重要,而每次迭代都是独立的则吞吐量很重要。
TL:DR这些数字是根据我在“典型”用例中所描绘的内容,在延迟,执行端口瓶颈和前端吞吐量(或诸如分支遗漏之类的停滞)之间进行权衡而得出的)。 请不要使用这些数字进行认真的性能分析。
- 0.5至1的按位/整数加法/减法/
移位和旋转(编译时const计数)/
所有这些的向量版本(每循环吞吐量1到4,1个循环延迟)
- 1个向量的最小值,最大值,比较相等,比较更大(创建掩码)
- 1.5个向量随机播放。Haswell和更新的服务器只有一个改组端口,在我看来,如果需要,通常需要大量改组,因此我将其加权更高些,以鼓励考虑使用更少的改组。他们不是免费的,尤其是。如果您需要内存中的pshufb控制掩码。
- 1.5加载/存储(L1缓存命中。吞吐量优于延迟)
- 1.75整数乘法(在Intel中,每1c吞吐量3c延迟/一,在AMD上为4c lat延迟,每2c tput仅一个)。小常数使用LEA和/或ADD / SUB / shift甚至更便宜。但是,当然,编译时常量总是很好的,并且经常可以优化为其他东西。(并且在循环中乘法通常可以由编译器降低强度
tmp += 7
,而不是在循环中tmp = i*7
)
- 1.75某些256b向量随机播放(insn上的额外延迟可能会在AVX向量的128b通道之间移动数据)。(或者在Ryzen上的3到7,其中的车道交叉点洗牌需要更多的机会)
- 2 fp add / sub(和相同版本的向量)(每循环吞吐量1或2,3至5个循环延迟)。如果您遇到延迟瓶颈,例如将一个只有1个
sum
变量的数组求和,则可能会很慢。(我可以根据情况将权重和fp mul低至1或高至5)。
- 2个向量fp mul或FMA。(如果启用了FMA支持,则x * y + z与mul或add一样便宜)。
- 2将通用寄存器插入/提取到向量元素(
_mm_insert_epi8
等)中
- 2.25 vector int mul(16位元素或pmaddubsw做8 * 8-> 16位)。在Skylake上更便宜,吞吐量比标量mul更好
- 可变计数2.25移位/旋转(2c延迟,Intel每2c吞吐量之一,在AMD或BMI2上更快)
- 2.5没有分支的比较(
y = x ? a : b
或y = x >= 0
)(test / setcc
或cmov
)
- 3 int->浮点转换
- 3完美预测的控制流(预测的分支,调用,返回)。
- 4向量int mul(32位元)(2微码,Haswell的10c延迟)
- 4整数除法或
%
除以编译时常数(非2的幂)。
- 7个向量水平操作(例如,
PHADD
在向量中添加值)
- 11(向量)FP分区(10-13c延迟,每7c吞吐量之一或更低)。(如果很少使用,可能会很便宜,但吞吐量比FP mul差6至40倍)
- 13吗 控制流(分支预测不佳,可能可预测75%)
- 13 int除法(是的,确实比FP除法慢,并且不能向量化)。(请注意,编译器使用mul / shift / add和魔术常数除以一个常数,而div / mod除以2的幂非常便宜。)
- 16(向量)FP sqrt
- 25吗 加载(L3缓存命中)。(缓存未命中存储比加载便宜。)
- 50吗 FP trig / exp / log。如果您需要大量的exp / log并且不需要完全的精度,则可以使用较短的多项式和/或表格来以精度为代价。 您也可以SIMD向量化。
- 50-80? 总是错误预测的分支,花费15-20个周期
- 200-400?加载/存储(缓存未命中)
- 3000 ??? 从文件读取页面(命中OS磁盘高速缓存)(在此处补全数字)
- 20000 ??? 磁盘读取页(操作系统磁盘高速缓存未命中,快速SSD)(完全由数字组成)
我完全是根据猜测来弥补的。如果看起来有问题,那是因为我在想一个不同的用例,或者是编辑错误。
AMD CPU的事物的相对成本将相似,不同之处在于,当移位计数可变时,它们具有更快的整数移位器。当然,由于种种原因,大多数代码中,AMD Bulldozer系列CPU的速度都较慢。(Ryzen在很多方面都很擅长)。
请记住,将事情归结为一维成本实际上是不可能的。除了缓存丢失和分支错误预测之外,代码块中的瓶颈还可能是延迟,总uop吞吐量(前端)或特定端口(执行端口)的吞吐量。
如果周围的代码使CPU忙于其他工作,则像FP划分这样的“慢”操作可能非常便宜。(向量FP div或sqrt各自为1 uop,它们的延迟和吞吐量都很差。它们仅阻塞除法单元,而不阻塞其所在的整个执行端口。整数div是几微码。)因此,如果您只有一个FP除法每增加约20 mul,CPU还有其他工作要做(例如独立循环迭代),则FP div的“成本”可能与FP mul差不多。这可能是您完成所有工作时吞吐量低的最佳示例,但由于总运算量较低,因此与其他代码很好地混合在一起(当延迟不是关键因素时)。
请注意,整数除法对周围的代码不那么友好:在Haswell上,它是9 oups,每8-11c吞吐量中有一个,延迟为22-29c。(64位划分为多慢,甚至SKYLAKE微架构)。所以延迟和吞吐量数字是有点类似FP格,但FP DIV只有一个微指令。
有关分析短时间的insn吞吐量,延迟和总计数的示例,请参见我的一些SO答案:
IDK如果其他人写SO答案,包括这种分析。我可以轻松地找到属于自己的时间,因为我知道我经常涉及到这一细节,并且我可以记住自己写的内容。