哪个编译成更快的代码:"n * 3" 还是 "n+(n*2)"?

7

哪个编译后的代码更快:“ans = n * 3”还是“ans = n +(n * 2)”?

假设n是int或long类型,并且运行在现代Win32 Intel计算机上。

如果涉及到一些取消引用,那么这是否会有所不同,即以下哪种方式更快?

long    a;
long    *pn;
long     ans;
... * pn = some_number; ans = * pn * 3;

还是

ans = * pn +(* pn * 2);

或者,这是不需要担心的事情,因为优化编译器很可能会在任何情况下都考虑到这一点?

11个回答

56

在我看来,这种微小的优化并不必要,除非你正在使用一些奇特的编译器。我认为可读性应该放在第一位。


15

没关系。现代处理器可以在一个时钟周期或更短的时间内执行整数乘法指令,而不像旧的处理器需要在内部执行一系列的位移和加法才能执行乘法,因此使用多个周期。我敢打赌

MUL EAX,3

执行速度较快

MOV EBX,EAX
SHL EAX,1
ADD EAX,EBX

在这种优化可能有用的最后一个处理器上可能是486。(是的,这对英特尔处理器有偏见,但可能也代表其他架构)。

无论如何,任何合理的编译器都应该能够生成最小/最快的代码。所以始终以可读性为先。


3
考虑到延迟和寄存器使用的限制,我真的怀疑MUL指令执行更快。此外,在x86上,任何合格的编译器都会使用LEA指令,而不是你所给出的三个指令序列,无论是3n还是n+2n。请注意,如果对于特定问题MUL确实比LEA更快,则编译器通常也会选择MUL指令。 - R.. GitHub STOP HELPING ICE
1
正确,但是LEA只在乘以一小组常数(如果我没记错的话是2、3、4、5、8和9)时才有用。无论如何,我的观点是让编译器找出最快的代码。 - Ferruccio

10

既然自己很容易测试,为什么不这样做呢?(使用cygwin中的gcctime

/* test1.c */
int main()
{
    int result = 0;
    int times = 1000000000;
    while (--times)
        result = result * 3;
    return result;
}

machine:~$ gcc -O2 test1.c -o test1
machine:~$ time ./test1.exe

real    0m0.673s
user    0m0.608s
sys     0m0.000s

测试几次,然后重复另一个案例。

如果想查看汇编代码,请使用gcc -S -O2 test1.c


不幸的是,这是一个糟糕的例子 - 使用i686-apple-darwin8-gcc-4.0.1,它会完全从循环中删除"result = result * 3",因为它总是零。将初始条件更改为"result = 1"可以得到更好的结果。 - Adam Rosenfield
创建一个随机数数组并进行处理,这样编译器就无法做出任何假设。 - DarenW

4

不难通过查看编译器的处理过程来了解代码的运行情况(这里使用的是DevStudio 2005)。编写一个简单的程序,包含以下代码:

int i = 45, j, k;
j = i * 3;
k = i + (i * 2);

在代码中间行上设置断点并使用调试器运行代码。当断点被触发时,右键单击源文件并选择“转到反汇编”。现在您将拥有一个包含CPU正在执行的代码的窗口。您会注意到,在这种情况下,最后两行产生完全相同的指令,即“lea eax,[ebx + ebx * 2]”(在这种特殊情况下不进行位移和添加)。在现代IA32 CPU上,由于CPU的流水线特性,使用直接MUL可能比位移更有效,因为使用修改后的值太早会产生惩罚。
这证明了aku所说的,即编译器足够聪明,可以为您的代码选择最佳指令。

我认为流水线不是问题。算术单元可能可以在一步内内部处理ebx+ebx*2。 - Artelius

4
这要取决于编译器、其配置和周围的代码。不应尝试猜测事物是否“更快”,而应进行测量。一般来说,现在不必担心这种纳米级优化问题——它几乎总是完全无关紧要的。如果您真正在一个需要关注此类问题的领域工作,您已经在使用分析器并查看编译器的汇编语言输出了。

1

这并不重要。我认为有更重要的事情需要优化。你花了多少时间思考和编写这个问题,而不是自己编码和测试呢?

:-)


1
只要你使用一个不错的优化编译器,就写一些易于编译器理解的代码。这样可以让编译器更容易地执行巧妙的优化。
你提出这个问题表明一个优化编译器比你更了解优化。所以相信编译器。使用 n * 3。
还可以看看this answer

1

这取决于您实际使用的编译器,但很可能它们会转换为相同的代码。

您可以通过创建一个小的测试程序并检查其反汇编来自行验证。


1

大多数编译器都足够聪明,可以将整数乘法分解为一系列位移和加法。我不知道Windows编译器的情况,但至少使用gcc,您可以让它输出汇编代码,如果您查看该代码,可能会看到两种编写方式的相同汇编代码。


0

编译器擅长优化像你这样的代码。任何现代编译器都会为两种情况生成相同的代码,并将* 2替换为左移。


不要那么确定 :) 我见过一些非常奇怪的嵌入式软件开发编译器。 - aku
1
在嵌入式系统中,几乎所有传统智慧都失效了。;-) - Konrad Rudolph

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接