哪些操作码在CPU级别上更快?[关闭]


19

在每种编程语言中,都有建议使用的操作码集。我已经尝试按照速度在这里列出它们。

  1. 按位
  2. 整数加/减
  3. 整数乘法/除法
  4. 比较方式
  5. 控制流
  6. 浮点加法/减法
  7. 浮点乘法/除法

在需要高性能代码的地方,可以对C ++进行手工优化,以使用SIMD指令或更有效的控制流,数据类型等进行汇编。因此,我试图了解数据类型(int32 / float32 / float64)还是所使用的操作(*+&)影响在CPU级别的性能。

  1. CPU上的单倍乘法是否比加法慢?
  2. 在MCU理论中,您了解到操作码的速度取决于执行所需的CPU周期数。那么,这是否意味着乘法需要4个周期,加法需要2个周期?
  3. 基本数学和控制流操作码的速度特性到底是什么?
  4. 如果两个操作码需要执行相同数量的周期,那么两者可以互换使用而没有任何性能增益/损失?
  5. 您可以分享有关x86 CPU性能的任何其他技术细节,

17
这听起来很像过早的优化,并且请记住,编译器不会输出您键入的内容,并且除非您确实确实有过,否则您确实不想编写汇编。
罗伊(Roy T.)

3
浮点乘法和除法是完全不同的东西,您不应将它们放在同一类别中。对于n位数字,乘法是O(n)进程,除法是O(nlogn)进程。这使得除法比现代CPU上的乘法慢5倍。
sam hocevar

1
唯一真正的答案是“配置它”。
Tetrad 2012年

1
扩大Roy的答案,手工优化装配几乎总是净亏损,除非您真的非常出色。现代CPU是非常复杂的野兽,优秀的优化编译器会进行完全不明显的代码转换,并且不会对手工进行琐碎的代码转换。即使对于SSE / SIMD,也始终在C / C ++中始终使用内部函数,并让编译器为您优化其用法。使用原始程序集会禁用编译器优化,导致损失惨重。
肖恩·米德迪奇

您无需手动优化即可使用SIMD进行装配。SIMD对于根据情况进行优化非常有用,但是使用SSE2有一个最标准的约定(至少适用于GCC和MSVC)。就您的清单而言,在现代的超标量多流水线处理器上,数据依赖性和寄存器压力比原始整数(有时甚至是浮点性能)引起更多的问题。数据局部性也是如此。顺便说一下,整数除法与现代x86上的乘法相同
OrgnlDave 2012年

Answers:


26

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上查看我的一些答案,例如thisthis

另外, 如果您喜欢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 : by = x >= 0)(test / setcccmov
  • 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答案,包括这种分析。我可以轻松地找到属于自己的时间,因为我知道我经常涉及到这一细节,并且我可以记住自己写的内容。


4处的“预测分支”有意义-20-25处的“预测分支”应该真正是什么?(我曾经以为错误预测的分支(列出的大约13个)比这要贵得多,但这就是为什么我在此页面上要学得更接近真相的原因-感谢这张桌子!!
马特

@Matt:我认为这是一个编辑错误,应该是“错误预测的分支”。感谢您指出了这一点。请注意,13是针对一个不完美预测的分支,而不是一个总是错误预测的分支,因此我对此进行了澄清。我重新挥了挥手,并进行了一些编辑。:P
Peter Cordes

16

这取决于所讨论的CPU,但是对于现代CPU,列表如下所示:

  1. 按位,加法,减法,比较,乘法
  2. 控制流(请参阅答案3)

根据CPU的不同,使用64位数据类型可能会付出巨大的代价。

你的问题:

  1. 在现代CPU上根本没有或没有多少。取决于CPU。
  2. 这些信息已经过时了20到30年(学校很烂,您现在有了证明),现代的CPU每个时钟处理可变数量的指令,多少取决于调度程序。
  3. 除法比其他方法慢一些,如果分支预测正确,则控制流非常快,如果分支预测正确,则控制流非常慢(大约20个周期,取决于CPU)。结果是很多代码主要受到控制流程的限制。不要if用算术合理地做。
  4. 任何一条指令执行多少个周期没有固定的数字,但是有时两条不同的指令可以相等地执行,将它们放在另一个上下文中,也许它们却不行,在不同的CPU上运行它们,您很可能会看到第三个结果。
  5. 除了控制流之外,另一个浪费时间的浪费是高速缓存未命中,每当您尝试读取不在高速缓存中的数据时,CPU将不得不等待从内存中获取数据。通常,您应该尝试同时处理彼此相邻的数据,而不是从各处提取数据。

最后,如果您正在制作游戏,请不要太担心所有这些,与砍CPU周期相比,最好专注于制作出色的游戏。


我还要指出的是,FPU非常快:特别是在Intel上-因此,仅在需要确定性结果时才真正需要定点。
乔纳森·迪金森

2
我只是将重点放在最后一部分-做一个好游戏。清除代码会有所帮助-这就是为什么3.仅在您实际衡量性能问题时才适用。如果需要,将这些if变成更好的东西总是很容易的。另一方面,5.则比较棘手-我绝对同意这是您首先要考虑的情况,因为这通常意味着更改体系结构。
a安2014年

3

我对x64_64上进行了百万次循环的整数运算进行了一次测试,得出如下简短结论,

增加--- 116微秒

sub ---- 116微秒

mul ---- 1036微秒

div ---- 13037微秒

上面的数据已经减少了循环引起的开销,


2

英特尔处理器手册可从其网站免费下载。它们很大,但是从技术上讲可以回答您的问题。尤其是您需要使用优化手册,但该说明手册还包含大多数主要CPU行用于simd指令的时序和等待时间,因为它们因芯片而异。

总的来说,我认为完整的分支以及指针追逐(链接列表遍历,调用虚拟函数)是性能杀手的顶端​​,但是与其他体系结构相比,x86 / x64 cpus在这两个方面都非常擅长。如果您要移植到另一个平台,则在编写高性能代码时,您会发现它们可能会造成多大的问题。


+1,相关负载(指针追逐)很重要。高速缓存未命中甚至会阻止将来的加载。一次运行中从主内存中获得许多负载所提供的带宽要比一次操作要求前者完全完成要好得多。
彼得·科德斯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.