在CPU上多次添加`a = a * 2 + b`指令?

时间:2012-02-11 16:51:58

标签: c assembly x86 multiplying fma

经典的Multiply-Accumulate操作是a = a + b*c。但我现在想知道是否存在允许在1个时钟周期内对整数执行以下操作的指令:( a和b是无符号的64位整数:unsigned long long int

a = a*2-1
a = a*2+b

目前,我使用:

a *= 2
--a

表示第一个和

a *= 2
a += b

表示第二个。我认为每个都被转换为ASM中的2条指令。但有没有办法使用1 ASM指令(以及在Intel CPU上使用哪个指令集扩展)?

(我搜索那个因为我这次操作数十亿次)

2 个答案:

答案 0 :(得分:7)

  1. 对于Intel CPU,请参阅LEA指令。它可以在一个指令中完成两个任务(不确定循环)。 (例如LEA EAX, [EAX*2+EBX])。请注意,这并非真正意味着乘法加法,因此它有趣的名称(加载有效地址)。

  2. 在C和C ++中,你不应该打扰。编译器会做它认为最好的事情,你可能只是阻碍它的努力。我老了a = a*2-1

  3. PS:如果你认为将某些东西翻译为两条指令,那么没有什么比查看装配更容易了。然后你会知道

答案 1 :(得分:1)

有许多体系结构可以在一条指令中执行此类操作。例如a*2 + b编译为

  • lea eax, [rsi+rdi*2]在x86-64上
  • add r0, r1, r0, lsl #1在ARM上
  • add w0, w1, w0, lsl 1在ARM64上
  • lda16 r0, r1[r0]在xcore上

编译器将适当地优化表达式。没有理由进行a *= 2; a += b之类的事情,这在许多情况下会降低可读性

您可以在Compiler Explorer

上查看演示

但是,如果您只是因为 进行了数十亿次操作 而要求这样做,那么这实际上是XY problem,因为更改C版本是不正确的减少指令数量的方法并不是减少运行时间。您不会通过指令数来衡量性能

现代CPU是标量代码和微代码,因此单个复杂指令可能比可以并行执行的多个简单指令要慢。编译器显然知道这一点,并且在编译时会考虑延迟。真正的解决方案是使用多线程和SIMD

例如Clang在AVX-512的主循环中发出以下指令

vpaddd  zmm0, zmm0, zmm0                            ; a *= 2
vpaddd  zmm1, zmm1, zmm1
vpaddd  zmm2, zmm2, zmm2
vpaddd  zmm3, zmm3, zmm3
vpaddd  zmm0, zmm0, zmmword ptr [rsi + 4*rdx]       ; a += b
vpaddd  zmm1, zmm1, zmmword ptr [rsi + 4*rdx + 64]
vpaddd  zmm2, zmm2, zmmword ptr [rsi + 4*rdx + 128]
vpaddd  zmm3, zmm3, zmmword ptr [rsi + 4*rdx + 192]

涉及循环展开和自动矢量化。每条指令一次可以处理 16 个32位整数。当然,如果您使用64位int,则它一次只能在“仅” 8上工作。此外,每个相同的指令都可以独立于其他指令执行,因此,如果CPU具有足够的执行端口,则它可以并行添加64个int。这就是我们所说的快速

GCC在展开循环时通常不太积极,并使用vpslld后跟vpaddd。但这仍然比标量版本要快。在带有霓虹灯的ARM上,您可以看到使用了shl v0.4s, v0.4s, 1; add v0.4s, v0.4s, v1.4s。这是Compiler Explorer demo link

与多线程相结合,比“优化”要快得多