以下哪一种技术是整数除2的最佳选择,为什么?
技巧1:
x = x >> 1;
技巧2:
x = x / 2;
这里x是整数。
以下哪一种技术是整数除2的最佳选择,为什么?
技巧1:
x = x >> 1;
技巧2:
x = x / 2;
这里x是整数。
当前回答
显然,如果你是在为下一个阅读你的代码的人写代码,那么你应该追求“x/2”的清晰度。
然而,如果速度是你的目标,那就两种方法都试一试,把握好时间。几个月前,我做了一个位图卷积例程,它涉及到在一个整数数组中步进,并将每个元素除以2。我做了各种各样的事情来优化它,包括用“x>>1”代替“x/2”的老技巧。
当我计算这两种方式时,我惊奇地发现x/2比x>>1快
这是使用Microsoft VS2008 c++并打开默认优化。
其他回答
Knuth说:
过早的优化是万恶之源。
所以我建议用x /= 2;
这样代码很容易理解,而且我认为这种形式的操作优化,对处理器来说不会有太大的区别。
使用除法(/),假设这样更清楚。编译器将相应地进行优化。
查看编译器的输出来帮助您做出决定。我在x86-64上使用 gcc (gcc) 4.2.1 20070719 [FreeBSD]
也可以在godbolt上看到编译器的在线输出。
What you see is the compiler does use a sarl (arithmetic right-shift) instruction in both cases, so it does recognize the similarity between the two expressions. If you use the divide, the compiler also needs to adjust for negative numbers. To do that it shifts the sign bit down to the lowest order bit, and adds that to the result. This fixes the off-by-one issue when shifting negative numbers, compared to what a divide would do. Since the divide case does 2 shifts, while the explicit shift case only does one, we can now explain some of the performance differences measured by other answers here.
C代码与汇编输出:
对于除法,你的输入是
int div2signed(int a) {
return a / 2;
}
这个编译成
movl %edi, %eax
shrl $31, %eax # (unsigned)x >> 31
addl %edi, %eax # tmp = x + (x<0)
sarl %eax # (x + 0 or 1) >> 1 arithmetic right shift
ret
shift也是一样
int shr2signed(int a) {
return a >> 1;
}
输出:
sarl %edi
movl %edi, %eax
ret
其他isa即使不能做得更好,也能同样有效地做到这一点。例如GCC For AArch64使用:
add w0, w0, w0, lsr 31 // x += (unsigned)x>>31
asr w0, w0, 1 // x >>= 1
ret
第一个看起来像除法吗?不。如果你想除法,用x / 2。如果可能的话,编译器可以优化它使用位移位(它被称为强度减少),这使得它成为一个无用的微优化,如果你自己做它。
哪一个是最好的选择,为什么整数除以2?
这取决于你对最佳的定义。
如果你想让你的同事讨厌你,或者让你的代码难以阅读,我肯定会选择第一个选择。
如果你想把一个数除以2,就用第二个数。
这两者是不等价的,如果数字是负的或在更大的表达式中,它们的行为是不一样的——bitshift的优先级比+或-低,除法的优先级更高。
您应该编写代码来表达其意图。如果您关心的是性能,不要担心,优化器在这类微优化方面做得很好。