哪个更好的select用于除以2的整数?
以下哪种技术是将整数除以2的最佳select,为什么?
技术1:
x = x >> 1;
技术2:
x = x / 2;
这里x
是一个整数。
使用最能描述你正在做什么的操作。
- 如果您将数字视为一系列位,请使用bitshift。
- 如果您将其视为数值,请使用除法。
请注意,它们并不完全相同。 他们可以给负面的整数不同的结果。 例如:
-5 / 2 = -2 -5 >> 1 = -3
(ideone)
第一个看起来像分裂吗? 不可以。如果你想分割,使用x / 2
。 如果可能的话,编译器可以对其进行优化以使用位移(这就是所谓的强度降低),如果你自己做,它就是无用的微型优化。
要堆积如山:有很多理由赞成使用x = x / 2;
这里有一些:
-
它更清楚地expression了你的意图(假设你不是在处理寄存器位的事情)
-
编译器会把这个减less到一个移位操作
-
即使编译器没有减less它,而是select了一个比shift更慢的操作,那么最终影响到程序性能的可能性本身就是微乎其微的(如果它确实影响了它,那么你就有一个实际的理由使用换class)
-
如果该部门将成为一个更大的expression式的一部分,如果使用除法运算符,则更有可能获得优先权:
x = x / 2 + 5; x = x >> 1 + 5; // not the same as above
-
有符号的算术可能会比上面提到的优先级问题更复杂
-
重申 – 无论如何,编译器已经为你做了这个。 事实上,它将把常数转换为一系列的转换,增加和乘以各种数字,而不仅仅是两个幂。 看到这个问题的链接,甚至更多的信息。
简而言之,如果你真的想要乘法或者除法,那么你可以通过编码来购买任何东西,除了增加引入错误的可能性。 由于编译器不够聪明,在适当的时候将这种事情优化到一个class次,这是一辈子的。
哪一个是最好的select,为什么把整数除以2?
取决于你最好的意思。
如果你想要你的同事恨你,或者让你的代码难以阅读,我一定会select第一个选项。
如果你想把一个数字除以2,那么去第二个数字。
两者不是等价的,如果数字是负数或者在较大的expression式内部,它们的行为不一样 – bitshift的优先级低于+
或-
,分区的优先级更高。
你应该写你的代码来expression它的意图是什么。 如果性能是你关心的,不用担心,优化器在这些微观优化方面做得很好。
只要使用分( /
),假设它更清晰。 编译器会相应地进行优化。
我同意你应该赞成x / 2
其他答案,因为它的意图更清晰,编译器应该为你优化它。
然而,偏好x / 2
x >> 1
x / 2
不是x >> 1
另一个原因是,如果x
是一个有符号int
并且是负数,那么>>
的行为是依赖于实现的。
ISO C99标准第5部分第6.5.7节:
E1 >> E2
的结果是E1
右移E2
位的位置。 如果E1
具有无符号types,或者如果E1
具有带符号types和非负值,则结果的值是E1
/ 2E2
的商的整数部分。 如果E1
有签名types和负值,则结果值是实现定义的。
x / 2
更清晰, x >> 1
速度并不快(根据微基准testing,Java JVM的速度提高了约30%)。 正如其他人所指出的,对于负数,四舍五入略有不同,所以当你想处理负数时你必须考虑这个问题。 一些编译器可能会自动将x / 2
转换为x >> 1
如果他们知道这个数字不能是负数(甚至认为我无法validation这一点)。
即使x / 2
不能使用(慢)除CPU指令,因为有些快捷方式是可能的 ,但它仍然比x >> 1
慢。
(这是一个C / C ++问题,其他编程语言有更多的运算符,对于Java也有无符号的右移x >>> 1
,这又是不同的,它允许正确计算两个值,所以(a + b) >>> 1
即使对于a
和b
非常大的值也会返回平均值,例如对于二进制search,如果数组索引可以变得非常大,就需要这个值。 许多版本的二进制search ,因为他们用(a + b) / 2
来计算平均值,这是行不通的,正确的解决方法是用(a + b) >>> 1
来代替。
Knuth说:
不成熟的优化是万恶之源。
所以我build议使用x /= 2;
这样的代码很容易理解,而且我认为这种forms的优化操作并不意味着处理器的巨大差异。
看看编译器输出来帮助你决定。 我在x86-64上运行了这个testing
gcc(GCC)4.2.1 20070719 [FreeBSD]
另请参见godbolt上的在线编译器输出 。
你所看到的是编译器在两种情况下都使用sarl
(算术右移)指令,所以它确实可以识别两个expression式之间的相似性。 如果使用除法,编译器也需要调整负数。 为此,将符号位向下移动到最低位,并将其添加到结果中。 这样在修改负数时就可以解决掉一个问题,相比之下,这个分歧会起什么作用。
既然分裂情况确实有两个变化,而显性变化情况只有一个变化,现在我们可以解释一下其他答案测量的一些性能差异。
带有汇编输出的C代码:
对于分歧,你的意见是
int div2signed(int a) { return a / 2; }
这编译到
movl %edi, %eax shrl $31, %eax addl %edi, %eax sarl %eax ret
类似的转变
int shr2signed(int a) { return a >> 1; }
输出:
sarl %edi movl %edi, %eax ret
只是一个附加说明 –
在一些基于虚拟机的语言中,x * = 0.5通常会更快 – 特别是actionscript,因为variables不需要被除数0检查。
使用x = x / 2;
OR x /= 2;
因为今后有可能是一个新的程序员在工作。 因此,他会更容易地找出代码行中发生了什么。 每个人都可能不知道这样的优化。
我说的是编程比赛的目的。 一般来说,他们有非常大的input,其中除以2发生多次,并知道input是正面还是负面。
x >> 1会比x / 2更好。 我在ideone.com上运行了一个程序,在这个程序中,有两个操作发生了10 ^ 10个分区。 x / 2花了将近5.5s,而x >> 1则花了将近2.6s。
我会说有几件事情要考虑。
-
Bitshift应该更快,因为实际上并不需要特殊的计算来移位,但正如所指出的那样,负数有潜在的问题。 如果你确保有正数,并且正在寻找速度,那么我会推荐移位。
-
师的操作人员很容易阅读。 所以如果你正在寻找代码的可读性,你可以使用它。 请注意,编译器优化领域已经走了很长的路,所以使代码易于阅读和理解是很好的做法。
- 根据底层硬件,操作可能有不同的速度。 安达尔的法则是使普通案件迅速。 所以你可能有硬件可以执行不同的操作比其他更快。 例如,乘以0.5可能会快于除以2.(如果您希望强制执行整数除法,您可能需要采取乘法的底线)。
如果你是在纯粹的performance后,我会build议创build一些testing,可以做数百万次的操作。 对你的OS /硬件/编译器/代码进行多次抽样(你的样本量),以确定哪一个在统计上最好。
就CPU而言,位移操作比分割操作快。 然而,编译器知道这一点,并将在可能的范围内进行适当的优化,因此,您可以采用最有意义的方式进行编码,并且知道您的代码正在高效运行。 但是请记住, unsigned int
可以(在某些情况下)比int
更好地优化,因为之前指出的原因。 如果你不需要签名算术,那么不要包含符号位。
x = x / 2; 是合适的代码使用..但一个操作取决于你自己的程序如何输出你想产生。
让你的意图更清楚…例如,如果你想分裂,使用X / 2,并让编译器优化它移动运营商(或其他任何东西)。
今天的处理器不会让这些优化对程序的性能产生任何影响。
答案将取决于你所处的环境。
- 如果你正在使用一个8位微控制器或者没有硬件支持乘法的任何东西,那么位移就是预料之中的,而且很平常,编译器几乎肯定会把
x /= 2
变成x >>= 1
,符号会在这种环境中引起更多的眉毛,而不是使用换class来实现分工。 - 如果您正在关键性能的环境或代码段中工作,或者您的代码可以通过编译优化来编译,
x >>= 1
,并且解释其推理的注释可能是为了清晰起见。 - 如果你不符合上述条件之一,只需使用
x /= 2
来使代码更易读。 更好地保存下一个程序员,看看你的代码10秒钟双重你的转换操作比不必要地certificate你知道这个转变是更有效的sans编译器优化。
所有这些都假定无符号整数。 简单的转变可能不是你想要签名的。 另外,DanielH提出了一些关于如ActionScript使用x *= 0.5
的好处。
mod 2,test for = 1。不要c中的语法。 但这可能是最快的。
一般右移分为:
q = i >> n; is the same as: q = i / 2**n;
这有时被用来加速程序的清晰度。 我不认为你应该这样做。 编译器足够聪明,可以自动执行加速。 这意味着, 以明确的代价来进行转变并不会带来任何益处 。
从实用的C ++编程看看这个页面。
显然,如果你正在为下一个阅读代码的人编写代码,那么清楚的是“x / 2”。
但是,如果速度是您的目标,请尝试两种方式和时间的结果。 几个月前我曾经研究过一个位图卷积例程,它涉及到一个整数数组,并将每个元素除以2.我做了各种各样的事情来优化它,包括用“x >> 1”代替“x / 2" 。
当我实际计时两种方式时,我惊奇地发现x / 2比x >> 1更快
这是使用Microsoft VS2008 C ++打开默认的优化。
在性能方面。 CPU的移位操作比划分操作码快得多。 因此,除以2或乘以2等都将从换class中受益。
至于外观和感觉。 作为工程师,我们变得如此依恋化妆品,即使美丽的女士们也不会使用! 🙂
X / Y是一个正确的…和“>>”移位运算符。如果我们想要两个整除我们可以使用(/)分配算子。 移位运算符用于移位。
X = X / 2; X / = 2; 我们可以这样使用
虽然x >> 1比x / 2更快,但在处理负值时正确使用>>要复杂一点。 它需要类似于以下内容:
// Extension Method public static class Global { public static int ShiftDivBy2(this int x) { return (x < 0 ? x + 1 : x) >> 1; } }