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

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

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

n % 2 == 0

然而,我的理解是 '%' 运算符实际上执行除法和 returns 它的余数;因此,对于上述情况,直接检查最后一位会更快。假设 n = 5;

5 = 00000101

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

n & 1 == 0

据我所知,这会比 % 2 更快,因为没有执行除法。需要一个简单的位比较。

那我有2个问题:

1) 第二种方式真的比第一种方式快吗(在所有情况下)?

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

速度相当

无论使用何种实现语言,无论整数是正数、负数还是零,模数版本通常都能保证正常工作。按位版本不是。

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

是的,位测试 比整数除法 by about a factor of 10 to 20, or even 100 for 128bit / 64bit = 64bit idiv on Intel 很多。特别是因为 x86 至少有一个 test 指令根据按位与的结果设置条件标志,所以你不必除法和 then 比较;按位 AND 比较。

我真的决定check the compiler output on Godbolt,得到了一个惊喜:

事实证明,使用 n % 2 作为带符号的整数值(例如 return signed int 的函数中的 return n % 2)而不是仅仅针对非-zero (if (n % 2)) 有时生成的代码比 return n & 1 慢。这是因为(-1 % 2) == -1,而(-1 & 1) == 1,所以编译器不能使用按位与。不过,编译器仍然避免整数除法,而是使用一些巧妙的 shift / and / add / sub sequence,因为这仍然比整数除法便宜。 (gcc 和 clang 使用不同的序列。)

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

使用 bool(必须是 0 或 1,而不仅仅是任何非零值)是另一种选择,但编译器必须对 return [=25] 做额外的工作=](或 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 thinks it's doing with all those branches.