用整数除以2,哪个选项更好?

以下哪一种技术是整数除2的最佳选择,为什么?

技巧1:

x = x >> 1;

技巧2:

x = x / 2;

这里x是一个整数。

69314 次浏览

只要使用除法(/),假设它更清楚。编译器将相应地进行优化。

第一个看起来像除法吗?不。如果你想除法,使用x / 2。如果可能的话,编译器可以优化它使用位移位(它被称为强度减少),这使得它成为一个无用的微优化,如果你自己做它。

x / 2更清晰,而x >> 1也没有快多少(根据一个微基准测试,对于Java JVM快30%左右)。正如其他人所注意到的,对于负数,舍入略有不同,所以当您想处理负数时必须考虑这一点。一些编译器可能会自动将x / 2转换为x >> 1,如果他们知道这个数字不可能是负数(即使我无法验证这一点)。

即使x / 2也可能不使用(慢)除法CPU指令,因为有些捷径是可能的,但它仍然比x >> 1慢。

(这是一个C / c++的问题,其他编程语言有更多的操作符。对于Java,也有无符号右移x >>> 1,这也是不同的。它允许正确计算两个值的平均值,因此即使ab的值非常大,(a + b) >>> 1也会返回平均值。这是必需的,例如,如果数组下标可以变得非常大。有许多版本的二分查找都存在bug,因为他们用(a + b) / 2来计算平均值。这不能正常工作。正确的解决方案是使用(a + b) >>> 1代替。)

使用最能描述您要做的事情的操作。

  • 如果你将数字作为一个比特序列来处理,请使用bitshift。
  • 如果你把它当作一个数值,使用除法。

请注意,它们并不完全相等。对于负整数,它们可以给出不同的结果。例如:

-5 / 2  = -2
-5 >> 1 = -3

(ideone)

哪一个是最好的选择,为什么整数除以2?

这取决于你对最好的的定义。

如果你想让你的同事讨厌你,或者让你的代码难以阅读,我肯定会选择第一个选择。

如果你想把一个数除以2,就用第二个数。

这两者是不等价的,如果数字为负数或在更大的表达式中,它们的行为是不一样的——bitshift的优先级低于+-,除法的优先级更高。

您应该编写代码来表达其意图。如果您关心的是性能,不要担心,优化器在这类微优化方面做得很好。

我同意其他答案,你应该支持x / 2,因为它的意图更清楚,编译器应该为你优化它。

然而,更喜欢x / 2而不是x >> 1的另一个原因是,如果x是有符号的int并且是负的,那么>>的行为是依赖于实现的。

ISO C99标准第6.5.7节第5项:

E1 >> E2的结果是E1右移的E2位位。如果E1有 无符号类型或如果E1具有符号类型和非负值, 结果的值是E1 /商的积分部分 2 __abc2。如果E1具有符号类型和负值,则结果值为 是由实现定义的。< / p >

有很多理由支持使用x = x / 2;,下面是一些:

  • 它更清楚地表达了您的意图(假设您不是在处理比特旋转寄存器位或其他东西)

  • 编译器无论如何都会将其简化为shift操作

  • 即使编译器没有减少它,并选择了比shift更慢的操作,这最终以可测量的方式影响程序性能的可能性本身也是微乎其微的(如果它确实影响了它,那么您就有了使用shift的实际理由)

  • 如果除法运算符是更大表达式的一部分,则使用除法运算符更有可能获得正确的优先级:

    x = x / 2 + 5;
    x = x >> 1 + 5;  // not the same as above
    
  • signed arithmetic might complicate things even more than the precedence problem mentioned above

  • to reiterate - the compiler will already do this for you anyway. In fact, it'll convert division by a constant to a series of shifts, adds, and multiplies for all sorts of numbers, not just powers of two. See this question for links to even more information about this.

In short, you buy nothing by coding a shift when you really mean to multiply or divide, except maybe an increased possibility of introducing a bug. It's been a lifetime since compilers weren't smart enough to optimize this kind of thing to a shift when appropriate.

X = X / 2;是合适的代码使用..但是一个操作取决于你自己的程序,你想要产生怎样的输出。

我说这些是为了参加编程比赛。一般来说,他们有非常大的输入,除以2会发生很多次,已知输入是正的或负的。

X >>1比X /2好。我在ideone.com上运行了一个程序,其中发生了超过10^10除以2的运算。X /2花了将近5.5s,而X >>1花了将近2.6s。

只是加了一个注释

在一些基于vm的语言中,x *= 0.5通常会更快——尤其是actionscript,因为变量不需要被检查是否除以0。

我想说有几件事需要考虑。

  1. Bitshift应该更快,因为没有特殊的计算是真的 需要移位的位,然而正如所指出的,有 负数的潜在问题。如果你有保证的话 正的数字,并且正在寻找速度,那么我会推荐 bitshift。李< / p > < / > 除法运算符对人来说很容易读懂。 因此,如果您正在寻找代码的可读性,您可以使用这个。请注意 编译器优化领域已经取得了长足的进步,因此编写代码变得更加容易 阅读和理解是很好的练习。

  2. 根据底层硬件, 操作可能有不同的速度。阿姆达尔定律是 一般情况快。所以你可能有可以运行的硬件 不同的操作比其他操作快。例如,乘以 0.5可能比除以2快。(当然,如果你想强制整数除法,你可能需要取乘法的底数)

如果您追求的是纯粹的性能,我建议您创建一些可以执行数百万次操作的测试。对执行进行多次采样(您的样本量),以确定哪一个在统计上最适合您的操作系统/硬件/编译器/代码。

Mod 2, test for = 1。不知道c中的语法,但这可能是最快的。

就CPU而言,位移位操作比除法操作快。 然而,编译器知道这一点,并将适当地优化到它可以的程度, 因此,您可以以最有意义的方式编写代码,并且知道您的代码是有意义的 有效地运行。但请记住,由于前面指出的原因,unsigned int(在某些情况下)可以比int更好地优化。 如果你不需要带符号的算术运算,那么不要包含符号位

使用x = x / 2;x /= 2;,因为将来可能会有新的程序员使用它。因此,他更容易发现代码行中发生了什么。每个人可能都不知道这种优化。

让你的意图更清楚……例如,如果你想除法,使用x / 2,并让编译器将其优化为shift运算符(或其他任何运算符)。

今天的处理器不会让这些优化对程序的性能产生任何影响。

一般右移分为:

q = i >> n; is the same as: q = i / 2**n;

这有时被用来加快程序的速度,但以清晰度为代价。我觉得你不应该这么做。编译器足够智能,可以自动执行加速。这意味着换一个班,你以清晰度为代价什么也得不到.;

看看这个页从实用c++编程。

在性能方面。CPU的移位运算比除法运算快得多。 所以除以2或乘以2等都可以从移位运算中获益。< / p >

至于外观和感觉。作为工程师,我们什么时候变得如此依赖化妆品,连漂亮的女士都不用!:)

这个问题的答案取决于你工作的环境。

  • 如果你在一个8位微控制器或任何没有硬件支持乘法的东西上工作,位移位是预期的和常见的,虽然编译器几乎肯定会将x /= 2转换为x >>= 1,但在那种环境中,除法符号的存在会比使用移位来实现除法更令人惊讶。
  • 如果你在一个性能关键的环境或代码段中工作,或者你的代码可以在编译器优化关闭的情况下编译,x >>= 1带注释解释它的推理可能是最好的,只是为了目的的清晰。
  • 如果你不处于上述条件之一,只需使用x /= 2使你的代码更具可读性。与其不必要地证明你知道没有编译器优化的shift更有效,不如让下一个恰巧看到你的代码的程序员花10秒钟反复考虑你的shift操作。

所有这些假设都是无符号整数。简单的移位可能不是你想要的符号。此外,DanielH提出了一个关于在ActionScript等特定语言中使用x *= 0.5的好观点。

Knuth说:

过早的优化是万恶之源。

所以我建议使用x /= 2;

这样代码很容易理解,而且我认为这种形式的操作优化,对处理器来说不会有太大的区别。

X/Y是正确的…和" >> "移位运算符..如果我们想要二除一个整数,我们可以使用(/)被除数运算符。移位运算符用于移位位。

< p > x = x / 2; x / = 2;我们可以这样使用..

看一下编译器的输出来帮助你决定。我用
在x86-64上运行这个测试 gcc (gcc) 4.2.1 20070719 [FreeBSD]

也可参见编译器在线输出在godbolt

你看到的是编译器在这两种情况下都使用了sarl(算术右移)指令,所以它确实识别出两个表达式之间的相似性。如果使用除法,编译器还需要为负数进行调整。为此,它将符号位移到最低阶位,并将其添加到结果中。与除法相比,这修复了移位负数时的差1问题。
由于除法情况需要2次移位,而显式移位情况只需要1次,我们现在可以解释用其他答案测量的一些性能差异

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。我做了各种各样的事情来优化它,包括用“x>>1”代替“x/2”的老技巧。

当我实际计算这两种方式时,我惊讶地发现X /2比X >>1快

这是使用Microsoft VS2008 c++并打开默认优化。