为什么n ++的执行速度比n = n + 1快?

时间:2010-05-21 18:57:30

标签: c micro-optimization optimization performance

在C语言中,为什么n++的执行速度比n=n+1快?

(int n=...;  n++;)
(int n=...;  n=n+1;)

我们的老师在今天的班上问了这个问题。 (这不是作业)

10 个答案:

答案 0 :(得分:102)

如果您正在使用“stone-age”编译器,那将是真的......

如果“石器时代”
++n的速度比n++n=n+1 机器通常有increment x以及add const to x

  • 如果是n++,您将只有2个内存访问权限(读取n,包含,写入n)
  • 如果是n=n+1,您将有3个内存访问权限(读取n,读取const,添加n和const,写入n)

但是今天的编译器会自动将n=n+1转换为++n,它会比您想象的更多!

同样在今天的无序处理器上 - 尽管“stone-age”编译器 - 运行时的情况在很多情况下可能不会受到影响

Related

答案 1 :(得分:41)

在G86 4.4.3 for x86上,使用或不使用优化,它们编译为完全相同的汇编代码,因此执行时间相同。正如您在程序集中看到的那样,GCC只需将n++转换为n=n+1,然后将其优化为单指令添加(在-O2中)。

您的教师建议n++更快只适用于非常古老的非优化编译器,这些编译器不够智能,无法为n = n + 1选择就地更新说明。这些编译器已经在PC世界中淘汰了多年,但仍然可以用于奇怪的专有嵌入式平台。

C代码:

int n;

void nplusplus() {
    n++;
}

void nplusone() {
    n = n + 1;
}

输出程序集(无优化):

    .file   "test.c"
    .comm   n,4,4
    .text
.globl nplusplus
    .type   nplusplus, @function
nplusplus:
    pushl   %ebp
    movl    %esp, %ebp
    movl    n, %eax
    addl    $1, %eax
    movl    %eax, n
    popl    %ebp
    ret
    .size   nplusplus, .-nplusplus
.globl nplusone
    .type   nplusone, @function
nplusone:
    pushl   %ebp
    movl    %esp, %ebp
    movl    n, %eax
    addl    $1, %eax
    movl    %eax, n
    popl    %ebp
    ret
    .size   nplusone, .-nplusone
    .ident  "GCC: (Ubuntu 4.4.3-4ubuntu5) 4.4.3"
    .section    .note.GNU-stack,"",@progbits

输出程序集(带有-O2优化):

    .file   "test.c"
    .text
    .p2align 4,,15
.globl nplusplus
    .type   nplusplus, @function
nplusplus:
    pushl   %ebp
    movl    %esp, %ebp
    addl    $1, n
    popl    %ebp
    ret
    .size   nplusplus, .-nplusplus
    .p2align 4,,15
.globl nplusone
    .type   nplusone, @function
nplusone:
    pushl   %ebp
    movl    %esp, %ebp
    addl    $1, n
    popl    %ebp
    ret
    .size   nplusone, .-nplusone
    .comm   n,4,4
    .ident  "GCC: (Ubuntu 4.4.3-4ubuntu5) 4.4.3"
    .section    .note.GNU-stack,"",@progbits

答案 2 :(得分:14)

编译器会将n + 1优化为虚无。

您的意思是n = n + 1吗?

如果是这样,他们将编译成相同的程序集。 (假设优化正在进行,并且它们是语句,而不是表达式)

答案 3 :(得分:5)

谁说它呢?你的编译器将它全部优化掉了,真的,使它成为一个没有实际意义的点。

答案 4 :(得分:3)

现代编译器应该能够将两种形式识别为等效形式,并将它们转换为最适合目标平台的格式。此规则有一个例外:具有副作用的变量访问。例如,如果n是一些内存映射的硬件寄存器,那么从中读取并写入它可能不仅仅是传输数据值(例如,读取可能会清除中断)。您可以使用volatile关键字让编译器知道优化对n的访问需要注意,在这种情况下,编译器可能会生成来自n++的不同代码(增量操作) )和n = n + 1(读取,添加和存储操作)。但是对于普通变量,编译器应该将两个表单优化为同一个。

答案 5 :(得分:2)

它并不是真的。编译器将根据目标体系结构进行更改。像这样的微优化通常具有可疑的好处,但重要的是,当然不值得程序员的时间。

答案 6 :(得分:2)

实际上,原因在于后置修复程序的运算符定义与预定义不同。 ++n将增加“n”并返回对“n”的引用,而n++将增加“n”将返回“{”的const副本。因此,短语n = n + 1将更有效率。但我不得不同意上述海报。好的编译器应该优化掉一个未使用的返回对象。

答案 7 :(得分:2)

在C语言中,n++表达式的副作用按照定义等同于n = n + 1表达式的副作用。由于您的代码仅依赖于副作用,因此很明显正确答案是这些表达式始终具有完全相同的性能。 (无论编译器中的任何优化设置如何,BTW,因为该问题与任何优化完全无关。)

只有当编译器故意(并且恶意地!)试图引入这种分歧时,才能实现这些表达式的任何实际性能差异。但在这种情况下,它可以采用任何一种方式,当然,无论编译者的作者想要歪曲它。

答案 8 :(得分:1)

我认为它更像是硬件问题而不是软件...如果我记得核心,在较旧的CPU中,n = n + 1需要两个内存位置,其中++ n只是一个微控制器命令......但我怀疑这适用于现代建筑......

答案 9 :(得分:0)

所有这些都取决于编译器/处理器/编译指令。所以做出任何假设“一般来说更快”并不是一个好主意。