以下哪种技术是整数除以 2 的最佳选择,为什么?
技术1:
x = x >> 1;
技术2:
x = x / 2;
这里 x
是一个整数。
x
,那么这种方式都不合适:它应该是 x >>= 1
或 x /= 2
,这取决于您打算用操作表达什么。不是因为它更快(任何现代编译器都会将所有等效变体编译为相同的快速汇编),而是因为它不那么令人困惑。
x = x >>> 1
。另请注意,根据平台和编译器,使用移位手动优化除法和乘法可能是相当合理的。 - 考虑微控制器,例如,不支持乘法运算的直接 ALU。
x /= 2
因为 x >>= 1
看起来太像 monadic bind ;)
x = x / 2
而不是 x /= 2
更具可读性。主观偏好也许:)
⬜=
组合的语言中,应尽可能使用这些组合。它消除了噪音并强调 x
已修改这一事实,而一般的 =
运算符则建议它采用独立于旧值的全新值。 — 始终避免使用组合运算符(以便它易于阅读,因此只知道数学运算符的人)可能也有其意义,但是您也需要放弃非常有用的 ++
、--
、+=
.
使用最能描述您正在尝试执行的操作的操作。
如果您将数字视为位序列,请使用 bitshift。
如果您将其视为数值,请使用除法。
请注意,它们并不完全相同。对于负整数,它们可以给出不同的结果。例如:
-5 / 2 = -2
-5 >> 1 = -3
第一个看起来像分裂吗?不可以。如果要除法,请使用 x / 2
。如果可能,编译器可以对其进行优化以使用位移(这称为强度降低),如果您自己进行,这将使其成为无用的微优化。
继续说:有很多理由支持使用 x = x / 2;
这里有一些:
它更清楚地表达了您的意图(假设您没有处理位旋转寄存器位或其他东西)
无论如何,编译器都会将其简化为移位操作
即使编译器没有减少它并选择了比 shift 更慢的操作,这最终以可测量的方式影响程序性能的可能性本身也非常小(如果它确实对它产生了可测量的影响,那么你有一个实际的使用轮班的理由)
如果除法将成为更大表达式的一部分,则如果使用除法运算符,则更有可能获得正确的优先级:x = x / 2 + 5; x = x >> 1 + 5; // 和上面不一样
有符号算术可能比上面提到的优先级问题更复杂
重申一下 - 无论如何,编译器都会为您执行此操作。实际上,它会将除以常数转换为一系列移位、加法和乘法运算,而不仅仅是 2 的幂。请参阅此问题以获取有关此问题的更多信息的链接。
简而言之,当你真的想要乘法或除法时,你编写一个班次代码什么都没有,除非可能会增加引入错误的可能性。这是一生,因为编译器不够聪明,无法在适当的时候将这种事情优化为转变。
a/b/c*d
形式的东西(其中 a..d
表示数字变量)而不是更具可读性的 (a*d)/(b*c)
。
a*d
或 b*c
有可能产生溢出,那么可读性较差的形式是不等价的,并且具有明显的优势。 PS我同意括号是你最好的朋友。
a/b/c*d
- 在溢出意味着数据存在严重错误的上下文中 - 而不是在 C 的性能关键块中代码)。
x
永远不会是奇数负数或不关心逐个错误时,代码 x=x/2;
才比 x>>=1
“更清晰”。否则 x=x/2;
和 x>>=1;
有不同的含义。如果需要的是 x>>=1
计算的值,我会认为它比 x = (x & ~1)/2
或 x = (x < 0) ? (x-1)/2 : x/2
或任何其他我能想到的使用除以二的公式更清楚。同样,如果需要 x/=2
计算的值,则比 ((x + ((unsigned)x>>31)>>1)
更清楚。
哪一个是最好的选择,为什么要将整数除以 2?
取决于你所说的最好是什么意思。
如果你想让你的同事讨厌你,或者让你的代码难以阅读,我肯定会选择第一个选项。
如果您想将一个数字除以 2,请使用第二个数字。
两者不等价,如果数字为负数或在较大的表达式中,它们的行为不同 - 位移的优先级低于 +
或 -
,除法的优先级更高。
您应该编写代码来表达其意图。如果您关心性能,请不要担心,优化器在这些微优化方面做得很好。
只需使用除法 (/
),假设它更清晰。编译器将相应地进行优化。
x >>= 1;
上使用 ASSUME(x >= 0); x /= 2;
之类的东西,但这仍然是一个重要的问题。
我同意您应该支持 x / 2
的其他答案,因为它的意图更清晰,编译器应该为您优化它。
但是,首选 x / 2
而不是 x >> 1
的另一个原因是,如果 x
是带符号的 int
并且为负数,则 >>
的行为取决于实现。
从 ISO C99 标准的第 6.5.7 节第 5 点:
E1 >> E2 的结果是 E1 右移 E2 位位置。如果 E1 具有无符号类型或 E1 具有有符号类型和非负值,则结果的值是 E1 / 2E2 的商的整数部分。如果 E1 具有带符号类型和负值,则结果值是实现定义的。
x>>scalepower
在负数上定义的行为正是将一个值除以 2 的幂以用于屏幕渲染等目的时所需要的行为,而使用 x/scalefactor
将是错误的,除非对负值应用更正。
>>
定义为算术右移,即移入符号位的副本以获取 2 的补码。 (例如 GNU C 保证:gcc.gnu.org/onlinedocs/gcc/Integers-implementation.html.)当然,Deathstation 9000 可以做一些无用的事情,但这对于大多数代码来说并不是真正重要的可移植性问题。 ISO C++ 甚至朝着将其标准化为算术右移的方向发展。
x / 2
更清晰,而 x >> 1
并没有快多少(根据微基准,Java JVM 快了大约 30%)。正如其他人所指出的,对于负数,舍入略有不同,因此在处理负数时必须考虑这一点。如果某些编译器知道数字不能为负数,则可能会自动将 x / 2
转换为 x >> 1
(甚至认为我无法验证这一点)。
甚至 x / 2
也可能不会使用(慢)除法 CPU 指令,因为 some shortcuts are possible,但它仍然比 x >> 1
慢。
(这是一个 C / C++ 问题,其他编程语言有更多的运算符。对于 Java 也有无符号右移 x >>> 1
,这又是不同的。它允许正确计算两个值的平均值(平均值),这样即使 a
和 b
的值非常大,(a + b) >>> 1
也会返回平均值。例如,如果数组索引可以变得非常大,则对于二进制搜索来说这是必需的。有 a bug in many versions of binary search,因为它们使用(a + b) / 2
来计算平均值。这不能正常工作。正确的解决方案是改用 (a + b) >>> 1
。)
x
可能为负数的情况下,编译器无法将 x/2
转换为 x>>1
。如果想要的是 x>>1
将计算的值,那几乎肯定会比任何涉及计算相同值的 x/2
的表达式更快。
x/2
转换为 x>>1
。我会尝试更新我的答案。
x/2
转换为 (x + (x<0?1:0)) >> 1
来避免 div
指令(其中 >> 是算术右移,以符号位移动)。这需要 4 条指令:复制值、shr(仅获取 reg 中的符号位)、添加、sar。 goo.gl/4F8Ms4
克努特说:
过早的优化是万恶之源。
所以我建议使用x /= 2;
这样代码很容易理解,而且我认为以这种形式优化这个操作,对处理器来说并没有太大的区别。
(n+8)>>4
可以很好地工作。您能否提供任何不使用带符号右移的清晰或高效的方法?
查看编译器输出以帮助您做出决定。我使用 gcc (GCC) 4.2.1 20070719 [FreeBSD] 在 x86-64 上运行了这个测试
另见compiler outputs online at godbolt。
您看到的是编译器在这两种情况下都使用 sarl
(算术右移)指令,因此它确实识别出两个表达式之间的相似性。如果使用除法,编译器还需要针对负数进行调整。为此,它将符号位向下移动到最低位,并将其添加到结果中。与除法相比,这解决了移位负数时的不合一问题。
由于除法情况进行 2 次移位,而显式移位情况仅进行一次,我们现在可以解释一些性能此处由其他答案衡量的差异。
带有汇编输出的 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
同样对于班次
int shr2signed(int a) {
return a >> 1;
}
输出:
sarl %edi
movl %edi, %eax
ret
其他 ISA 可以同样有效地做到这一点,甚至更多。例如 AArch64 的 GCC 使用:
add w0, w0, w0, lsr 31 // x += (unsigned)x>>31
asr w0, w0, 1 // x >>= 1
ret
d
的区域。这种划分对许多目的很有用。即使人们宁愿在 0 和 -1 之间以外的地方设置断点,添加偏移量也会移动它。满足第二个公理的整数除法会将数轴划分为大部分大小为 d
的区域,但其中一个大小为 2*d-1
。不完全“相等”的划分。你能提供关于oddball分区何时真正有用的建议吗?
sar
)。 goo.gl/KRgIkb。此邮件列表帖子 (gcc.gnu.org/ml/gcc/2000-04/msg00152.html) 确认 gcc 历来对有符号整数使用算术移位,因此 FreeBSD gcc 4.2.1 不太可能使用无符号移位。我更新了您的帖子以解决该问题,并且前面的段落说两者都使用了 shr,而实际上他们都使用了 SAR。 SHR 是它为 /
情况提取符号位的方式。还包括一个godbolt链接。
只是一个补充说明 -
x *= 0.5 在某些基于 VM 的语言中通常会更快——特别是 actionscript,因为不必检查变量是否被 0 整除。
eval
)每次都会重新发生这种情况。无论如何,是的,这是一个非常糟糕的测试,因为它是一个非常愚蠢的优化。
使用 x = x / 2;
或 x /= 2;
因为将来可能会有新程序员使用它。所以他会更容易找出代码行中发生了什么。大家可能不知道这样的优化。
我说的是编程竞赛的目的。通常,它们具有非常大的输入,其中除以 2 发生多次,并且已知输入是正数或负数。
x>>1 将优于 x/2。我通过运行一个程序检查了 ideone.com,其中发生了超过 10^10 除以 2 的操作。 x/2 花了将近 5.5 秒,而 x>>1 花了将近 2.6 秒。
x/2
优化为 x>>1
。对于有符号值,几乎所有实现都将 x>>1
定义为具有等效于 x/2
的含义,但当 x
为正时可以更快地计算,并且在 x
为负时与 x/2
有用地不同。
我想说有几件事需要考虑。
位移应该更快,因为实际上不需要特殊的计算来位移位,但是正如所指出的,负数存在潜在的问题。如果您确定有正数,并且正在寻找速度,那么我建议您使用 bitshift。除法运算符对人类来说非常容易阅读。因此,如果您正在寻找代码可读性,您可以使用它。请注意,编译器优化领域已经取得了长足的进步,因此使代码易于阅读和理解是一种很好的做法。根据底层硬件,操作可能有不同的速度。 Amdal 法则是让普通案件快速解决。因此,您可能拥有比其他硬件更快地执行不同操作的硬件。例如,乘以 0.5 可能比除以 2 更快。(当然,如果您希望强制执行整数除法,则可能需要乘法的下限)。
如果您追求纯粹的性能,我建议您创建一些可以执行数百万次操作的测试。对执行进行多次采样(您的样本大小),以确定哪一个在统计上最适合您的操作系统/硬件/编译器/代码。
>>
所做的匹配并且与 /
所做的不匹配,我也会推荐 bitshift。
就 CPU 而言,位移运算比除法运算快。但是,编译器知道这一点,并会在可能的范围内进行适当的优化,因此您可以以最有意义的方式进行编码,并且知道您的代码正在高效运行,因此您可以轻松地进行编码。但请记住,由于前面指出的原因,unsigned int
可以(在某些情况下)比 int
优化得更好。如果您不需要有符号算术,则不要包含符号位。
x = x / 2;是适合使用的代码.. 但操作取决于您自己的程序,您想如何产生输出。
让你的意图更清楚......例如,如果你想除法,使用 x / 2,并让编译器优化它以移位运算符(或其他任何东西)。
今天的处理器不会让这些优化对程序的性能产生任何影响。
这个问题的答案将取决于你工作的环境。
如果您正在使用 8 位微控制器或任何没有硬件支持乘法的东西,移位是预期的并且司空见惯,虽然编译器几乎肯定会将 x /= 2 转换为 x >>= 1,但除法的存在在那种环境中,符号会比使用移位来实现除法更令人惊讶。
如果您在性能关键的环境或代码部分中工作,或者您的代码可以在关闭编译器优化的情况下编译,x >>= 1 并带有解释其推理的注释可能只是为了清楚目的而最好的。
如果您不属于上述任何一种情况,只需使用 x /= 2 即可使您的代码更具可读性。下一个碰巧查看您的代码的程序员可以节省 10 秒重复操作的 shift 操作,而不是不必要地证明你知道这种转变是更有效的无编译器优化。
所有这些都假设无符号整数。简单的转变可能不是您想要的签名。此外,DanielH 提出了一个关于将 x *= 0.5
用于某些语言(如 ActionScript)的好观点。
mod 2,测试 = 1。不知道 c 中的语法。但这可能是最快的。
一般来说,右移分为:
q = i >> n; is the same as: q = i / 2**n;
这有时用于以清晰度为代价加快程序的速度。我认为你不应该这样做。编译器足够聪明,可以自动执行加速。这意味着换班不会以牺牲清晰度为代价。
看看这个page from Practical C++ Programming.
(x+128)>>8
将计算的 x
的值不接近最大值的值,如何在没有移位的情况下简洁地做到这一点?请注意,(x+128)/256
不起作用。你知道有什么好的表达方式吗?
显然,如果您正在为下一个阅读它的人编写代码,请确保“x/2”的清晰性。
但是,如果速度是您的目标,请尝试两种方式并计算结果的时间。几个月前,我研究了一个位图卷积例程,其中涉及遍历整数数组并将每个元素除以 2。我做了各种优化它,包括用“x>>1”替换“x”的老技巧/2"。
当我实际上对两种方式进行计时时,我惊讶地发现 x/2 比 x>>1 快
这是使用 Microsoft VS2008 C++ 并打开默认优化。
在性能方面。 CPU 的移位操作比除法操作码要快得多。因此除以 2 或乘以 2 等都受益于移位操作。
至于外观和感觉。作为工程师,我们什么时候变得如此痴迷于连美女都不用的化妆品! :)
X/Y 是正确的...和“>>”移位运算符..如果我们想要两个除以一个整数,我们可以使用 (/) 被除数运算符。移位运算符用于移位位..
x=x/2; x/=2;我们可以这样使用..
不定期副业成功案例分享
%
和/
运算符对于正数和负数操作数必须是一致的,因此无论a
和b
的符号如何,(a/b)*b+(a%b)==a
都为真。通常,作者会做出使 CPU 发挥最佳性能的选择。