我问了这个问题(链接)并得到了答案,现在我想知道是否应该改变我的方法,通常使用"(i << 1|1)"而不是"(2*i + 1)"?
由于ISO标准实际上并未强制执行性能要求,这将取决于实现、选择的编译器标志、目标CPU,并且很可能还会受到月相的影响。
这种优化(节省一些周期)在回报率方面几乎总是微不足道,而宏观级别的优化(如算法选择)则更具有意义。
首先追求代码的可读性。如果你的意图是移位和“或”,请使用移位版本。如果你的意图是进行乘法运算,使用“*”版本。只有在确定存在问题后才考虑性能。
任何一个像样的编译器都会比你更好地优化它 :-)
a *= 2; b*= 2
使用两个不同的操作,_恰好_因为它们是不同的! - MSalters这只是一个关于“...它将使用LEA
”所给答案的实验:
以下代码:
int main(int argc, char **argv)
{
#ifdef USE_SHIFTOR
return (argc << 1 | 1);
#else
return (2 * argc + 1);
#endif
}
gcc -fomit-frame-pointer -O8 -m{32|64}
(对于32位或64位)编译将产生以下汇编代码:
080483a0 <main>: 80483a0: 8b 44 24 04 mov 0x4(%esp),%eax 80483a4: 8d 44 00 01 lea 0x1(%eax,%eax,1),%eax 80483a8: c3 ret
00000000004004c0 <main>: 4004c0: 8d 44 3f 01 lea 0x1(%rdi,%rdi,1),%eax 4004c4: c3 retq
-DUSE_SHIFTOR
:080483a0 <main>: 80483a0: 8b 44 24 04 mov 0x4(%esp),%eax 80483a4: 01 c0 add %eax,%eax 80483a6: 83 c8 01 or $0x1,%eax 80483a9: c3 ret
-DUSE_SHIFTOR
:00000000004004c0 <main>: 4004c0: 8d 04 3f lea (%rdi,%rdi,1),%eax 4004c3: 83 c8 01 or $0x1,%eax 4004c6: c3 retq
LEA
。但是这两种情况的代码不同,原因有两个:
<<
或|
则不会。(x + 1) == (x | 1)
仅在!(x & 1)
时成立,否则加法会进位到下一位。一般来说,加一只会在一半的情况下将最低位设置为1。任何一个稍有头脑的编译器都会将这些表达式视为等价的,并将它们编译成相同的可执行代码。
通常情况下,不必过于担心优化这些简单的算术表达式,因为编译器最擅长优化这种类型的表达式。 (与许多其他情况不同,其中“聪明的编译器”可能会做正确的事情,但实际编译器却无法胜任。)
顺便说一句,在 PPC、Sparc 和 MIPS 上,这将变为相同的一对指令:移位后加上。 在 ARM 上,它将缩减为单个融合移位加操作,而在 x86 上,它可能是一个单独的 LEA
操作。
LEA EAX,EAX + EAX + 1
是最快的方法。 - GJ.使用-gcc命令行选项-S(未给出编译器标志)的输出:
.LCFI3:
movl 8(%ebp), %eax
addl %eax, %eax
orl $1, %eax
popl %ebp
ret
.LCFI1:
movl 8(%ebp), %eax
addl %eax, %eax
addl $1, %eax
popl %ebp
ret
我不确定哪一个是哪一个,但我认为这并不重要。
如果编译器没有进行任何优化,那么第二个可能会转换为更快的汇编指令。每个指令所需的时间完全取决于体系结构。大多数编译器将优化它们成为相同的汇编级指令。
我刚刚使用FrankH的源代码和gcc-4.7.1进行了测试,生成的代码如下:
lea 0x1(%rdi,%rdi,1),%eax
retq
没有人在意,也不应该在意。
别再担心这个了,把你的代码写好,简单明了,然后完成它。
i + i + 1
可能比其他两个更快,因为加法比乘法快,也可能比移位操作更快。
更快的是第一种形式(带有右移的那个),实际上,shr指令在最坏情况下需要4个时钟周期才能完成,而mul 10则在最好情况下。然而,最佳形式应由编译器决定,因为它可以完全了解其他(汇编)指令。