在正常流程或for
,if
等条件语句中使用按位运算是否会提高整体性能,并且在可能的情况下使用它们会更好吗?例如:
if(i++ & 1) {
}
与
if(i % 2) {
}
除非您使用的是古老的编译器,否则它已经可以自己处理这种级别的转换。也就是说,现代编译器可以并且将使用按位i % 2
指令实现AND
,只要在目标CPU上这样做是有意义的(公平地说,通常会这样做)。
换句话说,不要期望看到它们之间的性能有任何差异,至少对于具有相当合格的优化器的合理的现代编译器而言。在这种情况下,reasonably
也有一个相当广泛的定义 - 即使是几十年前的几个编译器也可以毫无困难地处理这种微优化。
TL; DR首先写入语义,然后优化测量的热点。
在CPU级别,整数模数和除法是最慢的操作之一。但是您不是在CPU级别编写,而是使用C ++编写,编译器将其转换为中间表示形式,最终根据您要编译的CPU模型将其转换为程序集。
在这个过程中,编译器将应用Peephole Optimizations,其中数字Strength Reduction Optimizations如(courtesy of Wikipedia):
Original 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
)。
这显然取决于CPU,但是您可以预期按位操作永远不会花费更多,并且通常需要更少的CPU周期来完成。一般来说,整数/
和%
都很慢,就像CPU指令一样。也就是说,现代CPU管道具有早期完成的特定指令并不意味着您的程序必须运行得更快。
最佳实践是编写可理解,可维护和表达其实现的逻辑的代码。这种微优化非常罕见,因此只有在分析表明存在严重瓶颈的情况下才应该使用它,并且这被证明会产生显着差异。此外,如果在某个特定平台上它确实产生了显着差异,那么当编译器优化器可以看到它是等效的时,它可能已经替换了按位运算。
默认情况下,您应该使用最能表达您的意图的操作,因为您应该优化可读代码。 (今天大多数时候最稀缺的资源是人类程序员。)
因此,如果提取位,则使用&
;如果测试可分性,则使用%
,即值是偶数还是奇数。
对于无符号值,两个操作都具有完全相同的效果,并且您的编译器应足够智能以通过相应的位操作替换除法。如果您担心,可以检查它生成的汇编代码。
不幸的是,整数除法在有符号值上略微不规则,因为它向零舍入,%的结果根据第一个操作数改变符号。另一方面,位操作总是向下舍入。因此编译器不能仅通过简单的位操作来替换除法。相反,它可以调用例程进行整数除法,或者用位操作替换它,并使用额外的逻辑来处理不规则性。这可能取决于优化级别以及哪些操作数是常量。
这种零不规则甚至可能是一件坏事,因为它是非线性的。例如,我最近遇到过一种情况,我们使用来自ADC的有符号值的除法,它必须在ARM Cortex M0上非常快。在这种情况下,最好用右移替换它,既可以提高性能,也可以摆脱非线性。
在“性能”方面,C运营商无法进行有意义的比较。在语言层面上没有“更快”或“更慢”的操作符这样的东西。只能对生成的编译机器代码进行性能分析。在您的具体示例中,生成的机器代码通常完全相同(如果我们忽略了第一个条件因某种原因包含后缀增量的事实),这意味着在任何情况下都不会有任何性能差异。
以下是两个选项的编译器(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)
所以4个额外的指令......对于prod环境来说什么都不是。这将是一个过早的优化,只是引入复杂性
按位运算要快得多。这就是编译器将为您使用按位运算的原因。实际上,我认为实现它会更快:
~i & 1
同样,如果你看看你的编译器生成的汇编代码,你可能会看到像x ^= x
而不是x=0
这样的东西。但是(我希望)你不会在你的C ++代码中使用它。
总而言之,做自己,以及需要维护代码的人,一个忙。使代码可读,让编译器进行这些微优化。它会做得更好。