在普通流程或条件语句(如 for
、if
等)中使用位运算是否会提高整体性能,并且在可能的情况下是否更好地使用它们?例如:
if(i++ & 1) {
}
对比。
if(i % 2) {
}
在普通流程或条件语句(如 for
、if
等)中使用位运算是否会提高整体性能,并且在可能的情况下是否更好地使用它们?例如:
if(i++ & 1) {
}
对比。
if(i % 2) {
}
除非你使用的是古老的编译器,否则它已经可以独立处理这个级别的转换。也就是说,现代编译器可以并将使用按位 AND
指令来实现 i % 2
,前提是在目标 CPU 上有意义(公平地说,通常是有意义的)。
换句话说,至少对于一个具有合理的优化器的相当现代的编译器而言,不要期望在这些代码之间看到任何性能差异。在这种情况下,“合理”还有一个相当广泛的定义--即使是几十年前的许多编译器也可以毫不费力地处理这种微小的优化。
a=b & 1;
在我所知道的所有符合标准的实现中都会比a=b % 2;
计算得更快,因为后者等价于a= b < 0 ? -(b & 1) : b & 1;
。如果结果仅用于测试是否为零,则优化器可能能够识别b<0
和b>=0
两种情况是等价的,但我特别不期望这种优化。 - supercata = b & 0b1000....0001
- potatob&1
的代码更复杂。 - supercatOriginal Calculation Replacement Calculation
y = x / 8 y = x >> 3
y = x * 64 y = x << 6
y = x * 2 y = x << 1
y = x * 15 y = (x << 4) - x
最后一个例子或许是最有趣的。虽然通过2的幂次方进行乘法或除法可以(手动)转换为位移操作,但编译器通常会教给你更聪明的转换方式,你可能自己想不到,并且这种转换也不容易被识别(至少我个人不能立即意识到 (x << 4) - x
等价于 x * 15
)。x * 15
== x * (16 - 1)
== (x * 16) - (x * 1)
== (x << 4) - x
。 - Yay295显然,这取决于CPU,但可以预期的是,按位操作完成所需的CPU周期永远不会更多,通常会更少。一般来说,整数除法和求余(/
和 %
)非常慢,就CPU指令而言。
最佳实践是编写可理解、可维护和表达其逻辑的代码。这种微小优化很少能够产生明显的影响,因此只有在分析表明存在关键瓶颈并且已经证明此优化能够产生显著影响时才应使用它。而且,如果在某些特定平台上确实产生了显著影响,则编译器优化器可能已经在可以看到等效性时替换为按位操作(这通常要求您通过一个常量进行除法或求余)。
无论值得与否,在特定于x86指令,且当除数是运行时变量值(因此不能轻松地优化为例如位移或按位与)时,/
和 %
操作所需的CPU周期可以在此处查找。这里无法列出太多x86兼容芯片,但以最近的CPU为任意例子-如果我们采用Agner的“Sunny Cove(Ice Lake)”(即第10代英特尔Core)数据,DIV和IDIV指令的延迟时间在12至19个周期之间,而按位与具有1个周期。在许多旧CPU上,DIV可能会慢40-60倍。
在“性能”方面,C运算符无法有意义地进行比较。从语言层面上来说,并没有什么“更快”或“更慢”的运算符。只有生成的机器代码可以被分析性能。在您的特定示例中,生成的机器代码通常完全相同(如果我们忽略第一个条件出于某种原因包含了后缀递增),这意味着性能上不会有任何差异。
总是有人谈论编译器的聪明程度,认为人们不应该考虑代码的性能,不敢质疑编译器的智慧,等等...结果就是人们相信每次使用% [某个二次幂]
时,编译器会神奇地将他们的代码转换为& ([某个二次幂] - 1)
。这简直是不真实的。如果一个共享库有这个函数:
int modulus (int a, int b) {
return a % b;
}
modulus(135, 16)
时,在编译后的代码中将没有任何位运算的痕迹。原因是什么呢?编译器很聪明,但它在编译库时并没有水晶球。它看到了一个通用的模数计算,对于只涉及二的幂次方的事实一无所知,因此保持不变。unsigned int modulus_2 (unsigned int a, unsigned int b) {
return a & (b - 1);
}
编译器无法为您完成此操作。
这是编译器(GCC 4.6)为两个选项生成的经过优化的-O3代码:
int i = 34567;
int opt1 = i++ & 1;
int opt2 = i % 2;
生成的 opt1 代码:
l %r1,520(%r11)
nilf %r1,1
st %r1,516(%r11)
asi 520(%r11),1
opt2的生成代码:
l %r1,520(%r11)
nilf %r1,2147483649
ltr %r1,%r1
jhe .L14
ahi %r1,-1
oilf %r1,4294967294
ahi %r1,1
.L14: st %r1,512(%r11)
div
的吞吐量和延迟与移位操作相比如何。这不是一个好的答案,缺乏解释,并且没有向读者提供任何信息。 - phuclv位运算速度更快。 这就是为什么编译器会为您使用位运算。 实际上,我认为将其实现为以下方式会更快:
~i & 1
x ^= x
而不是 x=0
这样的东西。但是(我希望)你不会在你的 C++ 代码中使用这个。(! i & 1)
与!i
完全相同。" - Potatoswatter~
,而不是!
。 - Potatoswatter
i
的后缀递增而第二个条件则不包括?如果两段代码的功能不一样,那么对它们进行性能比较是没有意义的。 - AnT stands with Russia