有很多架构可以在一条指令中执行此类操作。例如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 问题,因为更改 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 有足够的执行端口,它可以int
并行增加 64 秒。现在这就是我们所说的“快速”
GCC 在循环展开和使用vpslld
后跟vpaddd
. 但这仍然比标量版本快。在带有霓虹灯的 ARM 上,您可以看到shl v0.4s, v0.4s, 1; add v0.4s, v0.4s, v1.4s
它已被使用。这是编译器资源管理器演示链接
与比“优化”快得多的多线程相结合