C语言 在 CPU 上相乘加“a = a*2 + b”指令



经典的乘法累加运算是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 指令(以及英特尔 CPU 上的哪个指令集扩展(?

(我搜索它,因为我做了数十亿次此操作(

  1. 对于英特尔 CPU,请参阅LEA指令。它可以在一个指令中完成你的两个任务(虽然不确定周期(。(例如。 LEA EAX, [EAX*2+EBX] (。请注意,这并不是一个乘法加法,因此它的名字很有趣(加载有效地址(。

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

PS:如果您认为某些内容被翻译为两个指令,那么没有什么比查看程序集更容易的了。那你就知道了。

有很多架构可以在单个指令中执行此类操作。例如,a*2 + b编译为

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

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

您可以在编译器资源管理器上看到演示


但是,如果您仅仅因为执行此操作数十亿就要求这样做,那么这本质上是一个 XY 问题,因为更改 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。下面是编译器资源管理器演示链接

与多线程相结合,比您的"优化"快得多

相关内容

  • 没有找到相关文章

最新更新