哪个是用于将整数除以2的更好选项?

Abh*_*eet 401 c c++ optimization division micro-optimization

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

技巧1:

x = x >> 1;
Run Code Online (Sandbox Code Playgroud)

技术2:

x = x / 2;
Run Code Online (Sandbox Code Playgroud)

x是一个整数.

Mar*_*ers 845

使用最能描述您要执行的操作的操作.

  • 如果您将数字视为位序列,请使用bitshift.
  • 如果您将其视为数值,请使用除法.

请注意,它们并不完全等效.它们可以为负整数提供不同的结果.例如:

-5 / 2  = -2
-5 >> 1 = -3
Run Code Online (Sandbox Code Playgroud)

(ideone)

  • 在C++ 03中,两者都是针对负数定义的实现,*可能*给出相同的结果.在C++ 11中,除法是针对负数定义的,但转换仍然是实现定义的. (47认同)
  • 最初的问题对于"最佳"一词也含糊不清.在速度,可读性,欺骗学生的考试问题等方面"最好"......在没有"最佳"含义的解释的情况下,这似乎是最正确的答案. (20认同)
  • "实现定义"意味着编译器的实现者必须在几个实现选择中进行选择,通常具有实质性约束.这里,一个约束是`%`和`/`运算符必须对正负操作数都是一致的,这样无论符号是什么,`(a/b)*b +(a%b)== a`都是真的. `a`和`b`.通常,作者会做出选择,从CPU中获得最佳性能. (7认同)
  • 所以每个说"编译器都会将它转换为移位"的人都是错的,对吧?除非编译器可以保证您正在处理非负整数(无论是常量还是无符号整数),它都不能将其更改为一个班次 (6认同)
  • 虽然/ C的定义是在早期C标准中定义的实现(如果对负数进行向上或向下舍入).它必须始终与%(模/余数运算符)一致. (2认同)
  • 对于C/C++,Java,bc,expr,数学运算正确.但是对于Ruby,Python和Tcl,-5/2 = -3,而 - (5/2)= -2,反直觉但值得注意.见http://bugs.ruby-lang.org/issues/3289 (2认同)
  • @RBerteig此外,"实现定义"意味着需要实现来记录它的作用. (2认同)
  • @RBerteig每个程序都有文档.在"实现定义"的情况下,标准表示如果在这些情况下没有记录其行为,则实现是不符合的.(根据我的经验,大多数编译器在这方面是不符合的.否则他们的文档特别隐藏.) (2认同)

Cat*_*lus 225

第一个看起来像分裂吗?不.如果你想分开,请使用x / 2.如果可能的话,编译器可以对其进行优化以使用位移(称为强度降低),如果您自己进行,则会使其成为无用的微优化.

  • @exDM69:那是相关的,怎么样?我说"如果可能",而不是"永远".如果优化不正确,那么手动执行操作并不能使其正确(另外,如上所述,GCC足够聪明,可以找出对有符号整数的正确替换). (19认同)
  • 许多编译器不会将2的幂除以分段.对于有符号整数,这将是一个不正确的优化.您应该尝试查看编译器的汇编输出并亲自查看. (15认同)
  • @exDM69:许多编译器甚至会对有符号整数执行此操作,并根据签名调整它们.使用这些东西的好工具是:http://tinyurl.com/6uww253 (9认同)
  • 看看WikiPedia页面,这显然是有争议的,但我不会称之为强度降低.强度降低是指在循环中通过向循环中的先前值添加,从例如乘法到加法减少.这更像是一种窥视孔优化,编译器可以非常可靠地完成. (4认同)

Mic*_*urr 189

要坚持:有很多理由支持使用x = x / 2; 以下是一些:

  • 它更清楚地表达了你的意图(假设你没有处理比特错误的寄存器位或其他东西)

  • 无论如何,编译器会将其减少为移位操作

  • 即使编译器没有减少它并选择比移位更慢的操作,这最终会以可测量的方式影响程序性能的可能性本身也很小(如果它确实影响了它,那么你有一个实际的使用班次的原因)

  • 如果除法将成为更大表达式的一部分,那么如果使用除法运算符,则更有可能获得优先权:

    x = x / 2 + 5;
    x = x >> 1 + 5;  // not the same as above
    
    Run Code Online (Sandbox Code Playgroud)
  • 签名算术可能比上面提到的优先级问题更复杂

  • 重申 - 无论如何编译器已经为你做了这个.实际上,它会将除以常数转换为各种数字的一系列移位,加法和乘法,而不仅仅是2的幂.请参阅此问题以获取有关此内容的更多信息的链接.

简而言之,当你真正想要乘法或除法时,你不需要通过编码移动来购买任何东西,除非可能增加引入错误的可能性.这是一辈子的事情,因为编译器不够智能,无法在适当的时候优化这种转变.

  • 还有一点值得补充的是,虽然有优先规则,但使用括号并没有错.在修改一些生产代码时,我实际上看到了一些形式为'a/b/c*d`(其中`a..d`表示数字变量)而不是更易读的`(a*d)/(b*C)`. (5认同)
  • @JackManey,如果'a*d`或`b*c`有可能产生溢出,那么可读性较低的形式就不相同而且具有明显的优势.PS我同意括号是你最好的朋友. (4认同)

Luc*_*ore 62

哪一个是最佳选择以及为什么将整数除以2?

取决于你最好的意思.

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

如果要将数字除以2,请使用第二个数字.

这两个不等价,如果数字为负数或在较大的表达式内,它们的行为不相同 - bitshift的优先级低于+或者-,除法具有更高的优先级.

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


jus*_*tin 58

只需使用divide(/),假设它更清晰.编译器将相应地进行优化.

  • 编译器*应该*相应地进行优化. (34认同)
  • 如果编译器没有相应地进行优化,那么*应该*使用更好的编译器. (12认同)
  • @DavidStone:在什么处理器*可以*编译器优化除了1之外的任何常数的可能负有符号整数的除法,以便与移位一样高效? (3认同)
  • @supercat:这是一个很好的观点。当然,您可以将值存储在无符号整数中(我觉得与有符号/无符号不匹配警告结合使用时,它的声誉比它们应有的声誉要差得多),并且大多数编译器也有一种方法告诉它们在优化时假设某些内容是正确的。我更喜欢将其包装在兼容性宏中,并使用类似 `ASSUME(x >= 0); 的内容。x /= 2;` 优于 `x >>= 1;`,但这仍然是需要提出的重要一点。 (2认同)

jam*_*lin 38

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

然而,另一个原因宁愿x / 2x >> 1是的行为>>是实现相关的,如果x是签名int和为负.

从6.5.7节,ISO C99标准的第5章:

结果E1 >> E2E1右移位E2位置.如果E1具有无符号类型或者E1具有有符号类型和非负值,则结果的值是E1/ 2 的商的整数部分E2.如果E1具有有符号类型和负值,则结果值是实现定义的.

  • 值得注意的是,许多实现为负数上的'x >> scalepower`定义的行为正是在将值除以2的幂时用于屏幕渲染等目的所需的行为,同时使用`x/scalefactor`除非对正值进行修正,否则将是错误的. (3认同)

Tho*_*ler 32

x / 2更清晰,x >> 1速度也不快(根据微基准测试,Java JVM快30%).正如其他人所指出的那样,对于负数,舍入略有不同,所以当你想要处理负数时你必须考虑这个.有些编译器可能会自动转换x / 2x >> 1如果他们知道数字不能为负数(甚至认为我无法验证这一点).

甚至x / 2可能不会使用(慢)除法CPU指令,因为有些快捷方式是可能的,但它仍然比它慢x >> 1.

(这是一个C/C++问题,其他编程语言有更多的运算符.对于Java,还有无符号右移x >>> 1,这也是不同的.它允许正确计算两个值的平均值(平均值),这样就(a + b) >>> 1可以了即使对于非常大的a和值,也返回平均值b.如果数组索引可能变得非常大,则需要二进制搜索.在许多版本的二进制搜索中存在一个错误,因为它们用于(a + b) / 2计算平均值.工作正常.正确的解决方案是使用(a + b) >>> 1.)


小智 22

克努特说:

过早优化是万恶之源.

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

这样代码很容易理解,而且我认为以这种形式优化这个操作,并不意味着处理器有很大的不同.

  • 如果想要整数来维持公理(适用于自然数和实数)(n + d)/ d =(n/d)+,那么你会认为将数字缩减2的幂的首选方法是什么? 1?缩放图形时违反惯例会导致结果中出现明显的"接缝".如果想要一个统一且几乎对称于零的东西,`(n + 8)>> 4`就能很好地工作.您是否可以提供任何明确或有效的方法而不使用签名的右移? (4认同)

Mic*_*hue 19

看看编译器输出,以帮助您决定.我用
xcc(GCC)4.2.1 20070719 [FreeBSD] 在x86-64上运行了这个测试

另请参阅godbolt在线编译器输出.

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

带汇编输出的C代码:

对于鸿沟,您的输入将是

int div2signed(int a) {
  return a / 2;
}
Run Code Online (Sandbox Code Playgroud)

这就编译成了

    movl    %edi, %eax
    shrl    $31, %eax
    addl    %edi, %eax
    sarl    %eax
    ret
Run Code Online (Sandbox Code Playgroud)

同样的转变

int shr2signed(int a) {
  return a >> 1;
}
Run Code Online (Sandbox Code Playgroud)

输出:

    sarl    %edi
    movl    %edi, %eax
    ret
Run Code Online (Sandbox Code Playgroud)


ans*_*art 15

只是一个补充说明 -

在某些基于VM的语言中,x*= 0.5通常会更快 - 特别是actionscript,因为不必检查变量除以0.

  • 2不是变量. (4认同)
  • @minitech:这是一个糟糕的测试.测试中的所有代码都是不变的.在代码甚至是JIT之前,它将消除所有常量. (2认同)

Imd*_*dad 13

使用x = x / 2; OR x /= 2;因为将来新程序员可能会使用它.因此,他更容易找到代码行中发生的事情.每个人都可能不知道这种优化.


Sha*_*mar 12

我告诉编程比赛的目的.通常它们具有非常大的输入,其中除以2发生多次并且已知输入为正或负.

x >> 1将优于x/2.我通过运行一个程序来检查ideone.com,该程序进行了超过10 ^ 10除以2的操作.x/2花费了近5.5秒而x >> 1花费了近2.6秒的同一节目.


Jam*_*vec 12

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

  1. Bitshift应该更快,因为实际上不需要特殊的计算来移位位,但是正如所指出的那样,负数存在潜在的问题.如果你确保有正数,并且正在寻找速度,那么我会推荐bitshift.

  2. 除法运算符对人类来说非常容易阅读.因此,如果您正在寻找代码可读性,您可以使用它.请注意,编译器优化领域已经走过了漫长的道路,因此使代码易于阅读和理解是一种很好的做法.

  3. 根据底层硬件,操作可能具有不同的速度.Amdal的法律是使常见案例快速进行.因此,您可能拥有可以比其他操作更快地执行不同操作的硬件.例如,乘以0.5可能比除以2更快.(如果您希望强制执行整数除法,则可能需要采用乘法的底线).

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

  • "Bitshift应该更快".编译器将优化分区为位移 (2认同)

tyl*_*erl 12

就CPU而言,位移操作比除法操作更快.但是,编译器知道这一点,并将在适当的范围内进行适当的优化,因此您可以以最有意义的方式进行编码,并且知道您的代码是否有效运行.但请记住,unsigned int罐头(在某些情况下)的优化程度优于int之前指出的原因.如果您不需要签名算术,则不要包含符号位.


Goo*_*ner 11

x = x/2; 是适合使用的代码..但操作取决于您自己的程序,您希望如何生成输出.


小智 11

让你的意图更清晰...例如,如果你想分裂,使用x/2,让编译器优化它以移动运算符(或其他任何东西).

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


gki*_*sey 10

答案取决于您所处的环境.

  • 如果您正在使用8位微控制器或任何没有硬件支持进行乘法运算的东西,那么位移是预期的并且很普遍,虽然编译器几乎肯定会x /= 2变成x >>= 1,但是分区符号的存在会在该环境中引起更多的关注.使用转移实现分裂.
  • 如果您正在一个性能关键的环境或代码段中工作,或者您的代码可以通过编译器优化来编译,x >>= 1那么解释其推理的注释可能最好只是为了明确目的.
  • 如果您不符合上述条件之一,只需使用即可使代码更具可读性x /= 2.更好的是保存下一个程序员,他们碰巧看你的代码,你的班次操作10秒加倍,而不必毫无疑问地证明你知道转换是更有效的没有编译器优化.

所有这些都假设无符号整数.简单的转变可能不是你想签名的.此外,DanielH提出了一个关于使用x *= 0.5ActionScript等特定语言的好处.


cir*_*dei 8

mod 2,test for = 1. dunno c中的语法.但这可能是最快的.


Mou*_*hna 7

通常右移分为:

q = i >> n; is the same as: q = i / 2**n;
Run Code Online (Sandbox Code Playgroud)

这有时用于以清晰为代价加速程序.我认为你不应该这样做.编译器足够智能,可以自动执行加速.这意味着投入班次不会以牺牲清晰度为代价.

从Practical C++ Programming看一下这个页面.


Chr*_*net 7

显然,如果你是为下一个阅读它的人编写代码,那么请选择"x/2"的清晰度.

但是,如果速度是您的目标,请尝试两种方式和结果.几个月前,我研究了一个位图卷积例程,它涉及逐步遍历整数数组并将每个元素除以2.我做了各种各样的事情来优化它,包括用"x >> 1"代替"x"的旧技巧/ 2" .

当我实际上两种方式时,我惊讶地发现x/2比x >> 1更快

这是使用Microsoft VS2008 C++打开默认优化.