大多数编译器将% 2转换为位比较吗?真的更快吗?

Do most compilers transform % 2 into bit comparison? Is it really faster?

本文关键字:比较 真的 编译器 转换 大多数      更新时间:2023-10-16

在编程中,经常需要检查一个数字是奇数还是偶数。为此,我们通常使用:

n % 2 == 0

然而,我的理解是'%'运算符实际上执行除法并返回其余数;因此,对于上面的情况,直接检查最后一位会更快。比如n = 5;

5 = 00000101

为了检查数字是奇数还是偶数,我们只需要检查最后一位。如果是1,则数字为奇数;否则,它是偶数。在编程中,它可以这样表示:

n & 1 == 0

在我的理解中,这将比% 2更快,因为不执行除法。只需要进行位比较。

我有两个问题:

1)第二种方法是否真的比第一种方法快(在所有情况下)?

2)如果1的答案是肯定的,编译器(在所有语言中)是否足够聪明,可以将% 2转换为简单的位比较?或者,如果我们想要获得最佳性能,我们必须明确地使用第二种方式吗?

是的,位测试比整数除法快得多,大约是10到20倍,对于英特尔的128bit/64bit = 64bit,甚至是100倍。特别是,因为x86至少有一个test指令,它根据位与的结果设置条件标志,所以你不必除和然后比较;按位AND 比较。

我决定检查编译器在Godbolt上的输出,并得到了一个惊喜:

事实证明,使用n % 2作为有符号整数值(例如,从返回signed int的函数中获得return n % 2),而不是仅仅测试它的非零(if (n % 2)),有时会产生比return n & 1更慢的代码。这是因为(-1 % 2) == -1,而(-1 & 1) == 1,所以编译器不能使用位与。但是,编译器仍然避免整数除法,而是使用一些聪明的shift/和/add/sub序列,因为这仍然比整数除法便宜。(gcc和clang使用不同的序列)

因此,如果您想基于n % 2返回真值,最好的选择是使用unsigned类型。这使得编译器总是将其优化为单个AND指令。(在godbolt上,您可以切换到其他架构,如ARM和PowerPC,并看到unsigned even (%)函数和int even_bit(按位&)函数具有相同的asm代码。)

使用bool(必须是0或1,而不仅仅是任何非零值)是另一种选择,但编译器将不得不做额外的工作来返回(bool) (n % 4)(或n%2以外的任何测试)。它的位和版本将是0、1、2或3,因此编译器必须将任何非零值转换为1。(x86有一个有效的setcc指令,根据标志将寄存器设置为0或1,所以它仍然只有2个指令而不是1个。clang/gcc使用这个,参见godbolt asm输出中的aligned4_bool)

对于任何高于-O0的优化级别,gcc和clang将if (n%2)优化到我们期望的水平。另一个巨大的惊喜是icc 13 没有我不明白WTF icc认为它在做所有这些分支

速度相等

模版本通常保证无论整数是正、负或零都可以工作,而不管实现语言如何。

使用你认为最易读的内容。