我决定检查 C 语言,但相同的参数适用于 C++,类似的参数适用于 Java(Java 允许有符号溢出除外)。以下代码经过测试(对于 C++,将 _Bool 替换为 bool)。
_Bool approach1(int a, int b) {
return a > 0 || b > 0;
}
_Bool approach2(int a, int b) {
return (a + b) > 0;
}
这导致了反汇编。
.file "faster.c"
.text
.p2align 4,,15
.globl approach1
.type approach1, @function
approach1:
.LFB0:
.cfi_startproc
testl %edi, %edi
setg %al
testl %esi, %esi
setg %dl
orl %edx, %eax
ret
.cfi_endproc
.LFE0:
.size approach1, .-approach1
.p2align 4,,15
.globl approach2
.type approach2, @function
approach2:
.LFB1:
.cfi_startproc
addl %esi, %edi
testl %edi, %edi
setg %al
ret
.cfi_endproc
.LFE1:
.size approach2, .-approach2
.ident "GCC: (SUSE Linux) 4.8.1 20130909 [gcc-4_8-branch revision 202388]"
.section .note.GNU-stack,"",@progbits
即使考虑到如今的编译器有多聪明,这些代码也完全不同。为什么呢?好吧,原因很简单——它们并不相同。如果a 是-42 而b 是2,则第一种方法将返回true,第二种方法将返回false。
当然,您可能认为a 和b 应该是无符号的。
.file "faster.c"
.text
.p2align 4,,15
.globl approach1
.type approach1, @function
approach1:
.LFB0:
.cfi_startproc
orl %esi, %edi
setne %al
ret
.cfi_endproc
.LFE0:
.size approach1, .-approach1
.p2align 4,,15
.globl approach2
.type approach2, @function
approach2:
.LFB1:
.cfi_startproc
addl %esi, %edi
testl %edi, %edi
setne %al
ret
.cfi_endproc
.LFE1:
.size approach2, .-approach2
.ident "GCC: (SUSE Linux) 4.8.1 20130909 [gcc-4_8-branch revision 202388]"
.section .note.GNU-stack,"",@progbits
很容易注意到approach1在这里更好,因为它不会做无意义的加法,这实际上是非常错误的。事实上,它甚至对(a | b) != 0进行了优化,这是正确的优化。
在 C 中,定义了无符号溢出,因此编译器必须处理整数非常高的情况(尝试 INT_MAX 和 1 for approach2)。即使假设您知道数字不会溢出,也很容易注意到approach1 更快,因为它只是测试两个变量是否都是0。
相信你的编译器,它会比你优化得更好,而且没有你可能不小心写的小错误。编写代码而不是问自己 i++ 或 ++i 是否更快,或者x >> 1 或 x / 2 是否更快(顺便说一下,x >> 1 与 x / 2 的签名数字不同,因为舍入行为)。
如果您想优化某些东西,请优化您使用的算法。不要使用最坏情况 O(N4) 排序算法,而是使用最坏情况 O(N log N) 算法。这实际上会使程序更快,尤其是当您对相当大的数组进行排序时