为什么加法运算与现代处理器中的按位运算一样快?


72

我知道在现代处理器上按位运算是如此之快,因为它们可以并行地以32或64位进行操作,因此按位运算仅需一个时钟周期。但是加法是一个复杂的操作,至少包含一个(可能多达十二个)按位运算,因此我自然认为它会慢3-4倍。经过一个简单的基准测试,我惊讶地发现加法运算与任何按位运算(XOR,OR,AND等)完全一样快。谁能阐明这一点?




1
是的,乘法在我的测试中也非常快。它仅比加法慢2倍,而除法则慢30倍(!)。
SoloNasus

最新的并行前缀树加法器的简要概述:并行前缀网络的分类法,作者:David Harris:pages.hmc.edu/harris/research/taxonomy.pdf
Franki,

详细说明:博士Chen Chen的博士学位论文“用于二进制和模{2n-1,2n,2n + 1}加法器的并行前缀结构” digital.library.okstate.edu/etd/Chen_okstate_0664D_10070.pdf
Franki

Answers:


104

加法速度很快,因为CPU设计人员已加入了使其快速化所需的电路。与按位运算相比,它确实需要进行更多的门操作,但是它经常被CPU设计人员认为是值得的。参见https://en.wikipedia.org/wiki/Adder_(electronics)

两者都可以足够快地在单个CPU周期内执行。它们的速度不一样-加法比按位操作需要更多的门和更多的延迟-但它足够快,处理器可以在一个时钟周期内完成。指令解码和控制逻辑存在每条指令的等待时间开销,并且其等待时间明显大于执行按位运算的等待时间,因此两者之间的差异被该开销所淹没。 AProgrammer的答案Paul92的答案很好地解释了这些效果。


评论不作进一步讨论;此对话已转移至聊天
DW

38

有几个方面。

  • 按位运算和相加的相对成本。天真的加法器的门深度与单词的宽度成线性关系。有一些替代的方法会降低深度(IIRC的深度然后对数取决于单词的宽度),而这种方法的门成本更高。其他人已经提供了此类技术的参考,我只是指出,由于需要增加延迟的控制逻辑,因此差异也没有考虑到操作成本时那么重要。

  • 然后是一个事实,即处理器通常是有时钟的(我知道一些研究或特殊用途的无时钟设计,但我什至不确定某些产品是否可以在市场上买到)。这意味着无论操作速度如何,都将花费时钟周期的整数倍。

  • 最后,还有微体系结构方面的考虑:您确定要衡量所需的内容吗?如今,处理器倾向于流水线化,多标量,无序执行等。这意味着他们能够在完成的各个阶段同时执行几条指令。如果要通过度量显示某个操作比另一个操作花费更多的时间,则必须考虑这些方面,因为它们的目标是隐藏那些差异。使用独立数据时,添加和按位操作的吞吐量可能会完全相同,但是以其他方式衡量时延或引入操作之间的依赖关系可能会显示出来。而且,您还必须确保度量的瓶颈在于执行,而不是例如内存访问。


6
+1。是的,大多数处理器都是时钟驱动的,但是一些无时钟的CPU可以在市场上买到。
大卫·卡里

2
另一种可能性是,处理器可能将一个64位寄存器存储为一个16位块和三个17位块,其中每个块的额外位都从下面保留了递延进位。加法后再按位运算或存储可能需要1-2个额外的周期来传播进位,但是加法后再加法则则不需要。此外,在“商店”的情况下,额外的传播时间可能会延迟商店的性能,但是不需要代码“等待”它。
超级猫

3
@supercat Pentium 4做了类似的事情,使用了双倍速(相对于处理器的其余部分)的ALU,它可以将低16位或32位准备好用于后续操作,比上半位高半个周期。
Jeffrey Bosboom '17

2
您确定要测量什么吗? 在这种情况下,OP从测量得出的结论恰好对绝大多数CPU都是正确的。加法非常普遍,以至于超标量CPU在所有执行端口上都有加法器,而布尔值实现起来非常便宜(以晶体管数计),因此它们也出现在所有端口上。因此,add和boolean几乎始终具有相同的吞吐量(例如Intel Haswell中每个时钟4个)。
彼得·科德斯

2
尽管SIMD整数添加通常具有相同的延迟,但它们的吞吐量通常低于SIMD布尔值。从PentiumII到Broadwell的Intel CPU只能paddw在每个时钟2个运行vector-int加号(例如),但pand在每个时钟3个运行布尔值(如)。(Skylake在所有三个向量执行端口上都添加了向量加法器。)
Peter Cordes,

24

CPU循环运行。在每个周期中,都会发生一些事情。通常,一条指令需要花费更多的周期来执行,但同时会以不同的状态同时执行多条指令。

例如,一个简单的处理器可能为每个指令包含3个步骤:获取,执行和存储。在任何时候,都会处理3条指令:一条正在获取,一条正在执行,一条存储其结果。这称为管道,在此示例中分为3个阶段。现代处理器具有超过15个阶段的流水线。但是,加法以及大多数算术运算通常都在一个阶段中执行(我说的是ALU加2的运算,而不是指令本身-取决于处理器架构,指令可能需要从内存中提取参数,执行条件,将结果存储到内存的更多周期)。

一个周期的持续时间由最长的关键路径决定。基本上,这是准备阶段中某个阶段完成所需的最长时间。如果要使CPU更快,则需要优化关键路径。如果无法减小关键路径本身,则可以将其分为流水线的两个阶段,现在您可以以几乎两倍的频率为CPU计时(假设没有其他关键路径可以阻止您这样做) )。但这带来了开销:您需要在管道的各个阶段之间插入一个寄存器。这意味着您并没有真正获得2倍的速度(寄存器需要时间来存储数据),并且使整个设计变得复杂。

已经存在执行加法的相当有效的方法(例如,进位超前加法器),加法不是处理器速度的关键途径,因此将其分为多个周期没有意义。

另外,请注意,虽然这对您来说似乎很复杂,但是在硬件中,可以并行完成非常快的事情。


3
更长的流水线带来的巨大开销是需要更多的周期来从分支错误预测中恢复!如今,在级之间花费晶体管来缓冲数据的可能性很小。即使是简单的流水线CPU也必须在实际执行的指令之前进行获取/解码。如果CPU发现由于分支的运行方式与预期不同(或其他错误推测),导致前端使用了错误的代码,则它必须放弃该工作并从正确的指令开始。超标量乱序的CPU只会使情况变得更糟,因为它们可能在运行中有很多insn。
彼得·科德斯

12

处理器采用时钟控制,因此即使某些指令的执行速度明显快于其他指令,它们也可能花费相同的周期数。

您可能会发现,在寄存器和执行单元之间传输数据所需的电路比加法器复杂得多。

注意,简单的MOV(寄存器到寄存器)指令比按位逻辑执行的计算更少,但MOV和ADD通常都需要一个周期。如果将MOV的速度提高一倍,则CPU的时钟速度将提高一倍,而ADD将是两个周期。


评论不作进一步讨论;此对话已转移至聊天
吉尔斯(Gilles)

1
讨论摘要:一些乱序的CPU特别通过寄存器重命名处理MOV,有效地实现了零延迟。看到x86的MOV真的可以“免费”吗?为什么我根本无法复制?有关MOV实际成本的全部详细信息。
彼得·科德斯

12

加法非常重要,不要让它等待进位在64位累加器中波动:这是一个超前进位加法器,它们基本上是8位CPU(及其ALU)及更高版本的一部分。实际上,现代处理器往往也不需要太多的执行时间来进行完整乘法运算:进位超前实际上是处理器设计者工具箱中的一个非常古老的工具(且价格相对可承受)。


整数乘法肯定比x86上的ADD具有更高的延迟和更低的吞吐量。但是考虑到要构建一个快速乘法器需要多少个加法器,它的速度惊人的快:例如,自从Nehalem以来在Intel上以及自Ryzen以来在AMD上,8/16/32/64位标量整数乘以3个周期延迟,每1c吞吐一个(一个完整的流水线执行单元)。与每个时钟3或4个ADD吞吐量相比,这很糟糕,但与Intel Pentium P5中的9个周期IMUL延迟相比,这是惊人的。SIMD的情况与此类似:vector-int乘以比加法更高的延迟和更低的吞吐量,但是仍然很快。
彼得·科德斯

因此,是的,相对于其他指令,乘法现在比现在昂贵得多。避免花费超过2条指令的代价通常是不值得的,有时甚至连2条指令的替代都不值得(例如使用shift + add lea指令)。
彼得·科德斯

9

我认为您很难找到一个处理器,其加法运算比按位运算要多。部分原因是大多数处理器必须在每个指令周期内至少执行一次加法运算,只是为了增加程序计数器的数量。仅仅按位操作并没有那么有用。

(指令周期而不是时钟周期-例如6502由于未流水线且没有指令缓存,因此每条指令至少需要两个时钟周期)

您可能缺少的真正概念是关键路径:在芯片内,可能在一个周期内执行的最长操作在硬件级别上决定了芯片的时钟频率。

例外情况是(很少使用和很少商业化的)异步逻辑,它确实会根据逻辑传播时间,设备温度等以不同的速度执行。


这不是用户可控制的按位操作,但是8086上的某些指令(例如,清除中断标志)花费的时间少于整数加法的时间。更抽象地讲,所有指令都是一个字的RISC系统可以为PC使用一个简单的二进制计数器,这比通用加法器电路快得多。
标记

与加法算术指令相比,程序计数器的加法往往非常简单,因为其中一个操作数较小(指令大小或相对跳转偏移量也受大小限制)
Ben Voigt

对6502进行了流水线处理-它在上一条指令的最后一个周期内读取了下一条指令的第一个字节。否则,取/解码/执行将至少是三个周期。
gnasher729

8

在门级,您正确地进行加法需要更多的工作,因此需要更长的时间是正确的。但是,该成本是无关紧要的,无关紧要。

现代处理器采用时钟。除了该时钟频率的倍数之外,您无法执行任何其他操作。如果将时钟速率提高,以最大程度地提高按位运算的速度,则必须花费至少2个周期进行加法运算。由于您实际上并不需要整个2个周期的时间,因此大部分时间将花在等待周围。您只需要1.1(或类似的数字)。现在,您的芯片添加速度比市场上其他任何产品都要慢。

更糟糕的是,仅添加或执行按位运算的行为只是一个周期中发生的一小部分。您必须能够在一个周期内获取/解码指令。您必须能够在一个周期内执行缓存操作。许多其他事情与简单的加法或按位运算在相同的时间范围内进行。

当然,解决方案是开发一个庞大的深度管道,将这些任务分解成适合按位运算定义的微小循环时间的微小部分。奔腾4以这些深入的术语表达了思想的局限性。出现各种问题。尤其是,分支变得异常困难,因为一旦拥有确定要采用哪个分支的数据,就必须冲洗管道。


7

现代处理器采用时钟控制:每个操作都需要一定数量的时钟周期。处理器的设计者确定时钟周期的长度。那里有两个考虑因素:一是硬件的速度,例如以单个NAND门的延迟来衡量。这取决于所使用的技术以及诸如速度与功率使用之间的权衡。它独立于处理器设计。第二,设计人员确定一个时钟周期的长度等于单个NAND门的n个延迟,其中n可能是10或30,或任何其他值。

该选择n限制了可以在一个周期内处理的复杂操作的程度。将有16个但不能以15个NAND延迟完成的操作。因此,选择n = 16表示可以在一个周期内完成此操作,选择n = 15表示无法进行此操作。

设计人员将选择n,以便许多重要的操作可以在一个或两个或三个周期内完成。n将被选择为局部最优:如果将n替换为n-1,则大多数操作会快一些,但是某些操作(确实需要完整的n个NAND延迟的操作)会慢一些。如果很少有操作会减慢速度,从而使平均程序执行速度更快,那么您将选择n-1。您也可以选择n + 1。这会使大多数操作变慢一些,但是如果您有许多操作无法在n个延迟内完成,但可以在n + 1个延迟内完成,那么它将使处理器整体速度更快。

现在您的问题:加法和减法是如此常见,以至于您希望能够在一个周期内执行它们。结果,AND,OR等执行速度更快并不重要:他们仍然需要一个周期。当然,“计算”“与”,“或”等单位有很多时间来打动拇指,但这无济于事。

请注意,不仅可以在n个NAND延迟内完成操作:例如,可以通过增加一些聪明点来加快添加速度,通过增加聪明点来加快速度,通过投入大量的硬件来加快速度。 ,最后一个处理器可能包含非常快,非常昂贵的电路以及更慢和更便宜的电路,因此可以通过花更多的钱来使一个操作足够快。

现在,您可以使时钟速度如此之高/周期如此之短,以至于只有简单的位操作在一个周期内执行,而其他所有操作都在两个或更多周期内执行。这很可能会减慢处理器的速度。对于需要两个周期的操作,通常将不完整的指令从一个周期移至下一个周期会产生开销,因此两个周期并不意味着您有两倍的执行时间。因此,要在两个周期内进行加法运算,就无法使时钟速度加倍。


6

让我纠正一些在您现有答案中未明确提及的事情:

我知道在现代处理器上按位运算是如此之快,因为它们可以并行运行在32位或64位上,

这是真的。通常将CPU标记为“ XX”位(并非总是如此)意味着它的大多数通用结构(寄存器宽度,可寻址RAM等)的大小均为XX位(通常为“ +/- 1”或类似大小)。但是关于您的问题,您可以放心地假设具有32位或64位的CPU将在恒定时间内对32位或64位进行任何基本位操作。

因此按位运算仅需一个时钟周期。

这个结论不一定是事实。尤其是具有丰富指令集(google CISC与RISC)的CPU,即使是简单的命令,也很容易占用多个周期。通过交织,甚至简单的命令也可能会分解为3个时钟的fetch-exec-store(作为示例)。

但是加法是一个复杂的操作

不,整数加法是一个简单的操作;减法也是如此。在全硬件中实现加法器非常容易,并且它们像基本位操作一样立即执行自己的工作。

它至少包含一个(可能多达十二个)按位运算,所以我自然认为它会慢3-4倍。

它将占用三到四倍的晶体管,但是相比之下,可以忽略的整体情况。

经过一个简单的基准测试,我惊讶地发现加法运算与任何按位运算(XOR,OR,AND等)完全一样快。谁能阐明这一点?

是:整数加法按位运算(比其他数多一些,但仍然有一点)。不需要分阶段进行任何操作,也不需要复杂的算法,时钟或其他任何方法。

如果您希望添加比CPU体系结构更多的位,则必须分阶段进行。但这是另一个复杂性级别(编程语言级别,而不是汇编/机器代码级别)。在过去(或今天在小型嵌入式CPU上),这是一个常见问题。对于PC等,对于最常见的数据类型,它们的32位或64位就足够了,这开始成为讨论的重点。


有趣的是,将加法时间从O(N)减少到O(sqrt(N))并不会显着增加所需的晶体管数量或布线复杂度(每个阶段只需要让一根导线从下方潜入即可) ,并且需要额外的sqrt(N)合并阶段。时间成本可以以O(lgN)晶体管的成本降低为O(lgN),但是在许多情况下,处理诸如64-位加法,例如8个8位加法(使用sqrtN转发)结合了三层合并逻辑,而不是64个1位加法与六层合并
。– supercat

是的,加法器非常简单。真正令人印象深刻的是具有完全流水线式3周期延迟64位整数乘法器的现代x86 CPU 。(例如imul rax, rcx,在Intel Sandybridge系列和AMD Ryzen上具有3c延迟,每1c吞吐量有1个)。即使是64位全乘法(在rdx:rax中生成128位结果)也具有相同的延迟和吞吐量,但实现为2微码(在不同端口上并行运行)。(有关说明表和出色的微体系结构指南,请参见agner.org/optimize)。
彼得·科德斯

[随身携带]处于另一个复杂度级别(编程语言级别,而不是汇编/机器代码级别。这取决于语言。针对16位CPU的AC编译器在编译时必须为您发出add / adc uint32_t这是两个值的加法,今天对于32位目标上的int64_t仍然适用。AVR是8位RISC微控制器,因此32位整数需要4条指令:godbolt.org/g/wre0fM
Peter Cordes

是的,@ PeterCordes,这就是我的意思,我已经澄清了我的句子。
AnoE
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.