C++中的临时变量和性能

4

假设我们有两个函数:

int f();
int g();

我希望得到f()和g()的总和。

第一种方式:

int fRes = f();
int gRes = g();
int sum = fRes + gRes;

第二种方法:

int sum = f() + g();

这两种情况的性能有什么区别吗?

对于复杂类型而不是整数,同样的问题。

编辑

我理解得对吗?在这种情况下(包括频繁执行的任务),我不必担心性能,并使用临时变量来增加可读性和简化代码?


3
错误的问题。问题应该是“我应该担心这里的表现吗?”,而答案是“绝对不用”。 - user395760
7个回答

11

你可以通过编译成汇编语言(当然要启用优化)并检查输出来自己回答这样的问题。如果我将你的示例完整地扩展为可编译程序...

extern int f();
extern int g();
int direct()
{ 
  return f() + g(); 
}
int indirect()
{
  int F = f();
  int G = g();
  return F + G;
}

并编译它(g++ -S -O2 -fomit-frame-pointer -fno-exceptions test.cc;最后两个开关从输出中消除了许多干扰项),我得到了这个结果(进一步删除了干扰项):

__Z8indirectv:
        pushq   %rbx
        call    __Z1fv
        movl    %eax, %ebx
        call    __Z1gv
        addl    %ebx, %eax
        popq    %rbx
        ret

__Z6directv:
        pushq   %rbx
        call    __Z1fv
        movl    %eax, %ebx
        call    __Z1gv
        addl    %ebx, %eax
        popq    %rbx
        ret

正如你所看到的,这两个函数生成的代码相同,因此你的问题的答案是否定的,不会有性能差异。现在让我们来看看复数——相同的代码,但是在整个代码中将 s/int/std::complex<double>/g,并在顶部添加#include <complex>;编译开关也相同。

__Z8indirectv:
        subq    $72, %rsp
        call    __Z1fv
        movsd   %xmm0, (%rsp)
        movsd   %xmm1, 8(%rsp)
        movq    (%rsp), %rax
        movq    %rax, 48(%rsp)
        movq    8(%rsp), %rax
        movq    %rax, 56(%rsp)
        call    __Z1gv
        movsd   %xmm0, (%rsp)
        movsd   %xmm1, 8(%rsp)
        movq    (%rsp), %rax
        movq    %rax, 32(%rsp)
        movq    8(%rsp), %rax
        movq    %rax, 40(%rsp)
        movsd   48(%rsp), %xmm0
        addsd   32(%rsp), %xmm0
        movsd   56(%rsp), %xmm1
        addsd   40(%rsp), %xmm1
        addq    $72, %rsp
        ret

__Z6directv:
        subq    $72, %rsp
        call    __Z1gv
        movsd   %xmm0, (%rsp)
        movsd   %xmm1, 8(%rsp)
        movq    (%rsp), %rax
        movq    %rax, 32(%rsp)
        movq    8(%rsp), %rax
        movq    %rax, 40(%rsp)
        call    __Z1fv
        movsd   %xmm0, (%rsp)
        movsd   %xmm1, 8(%rsp)
        movq    (%rsp), %rax
        movq    %rax, 48(%rsp)
        movq    8(%rsp), %rax
        movq    %rax, 56(%rsp)
        movsd   48(%rsp), %xmm0
        addsd   32(%rsp), %xmm0
        movsd   56(%rsp), %xmm1
        addsd   40(%rsp), %xmm1
        addq    $72, %rsp
        ret

指令数量大大增加了,编译器似乎没有进行完美的优化工作,但是两个函数生成的代码是相同的。


+1 个实际证据。需要注意的是,编译器通常能够理解您正在做什么,并产生相同的结果,从而使代码更易读。 - Sion Sheevok
并不意味着生成的代码在两种情况下必须相同。 - Maxim Egorushkin
好的,实际上并不能保证生成的代码是相同的。如果你使用gcc的-O0而不是-O2,我会预计会有所不同。但是,将它们生成等效的代码是书中最基本的优化之一(通常称为“前向传播”),当启用优化时,现代编译器不执行此操作是不适合的。 - zwol

4

我认为第二种方式是在函数返回值时将其分配给一个临时变量。然而,当您需要多次使用f()g()的值时,将它们存储到变量中而不是每次重新计算可能会更有帮助。


2

根据你所写的内容,它们之间只有微小的差异(另一个答案已经解决了这个问题,其中一个具有序列点)。

如果你改为以下方式,则它们将不同:

int fRes;
int gRes;
fRes = f();
fRes = g();
int sum = fRes + gRes;

(假设 int 实际上是具有非平凡默认构造函数的其他类型。)
在这种情况下,您会调用默认构造函数,然后再调用赋值运算符,这可能需要更多的工作。

2

如果你关闭了优化,可能会有差异。如果开启了优化,它们很可能会导致相同的代码。特别是当您将fResgRes标记为const时,这一点尤为明显。

因为编���器可以省略对复制构造函数的调用,如果fResgRes是复杂类型,它们在性能上也不会有区别。

有人提到过多次使用fResgRes。当然,这很明显可能不太优化,因为您必须多次调用f()g()


1

这完全取决于编译器进行了哪些优化。这两者可能会编译成略有不同或完全相同的字节码。即使稍有不同,对于这些特定样本,您也无法测量出统计上显著的时间和空间成本差异。


1

在我的平台上,开启了完全优化的情况下,一个返回两个不同情况下sum的函数编译成了完全相同的机器码。

这两个例子之间唯一的小差别是第一个保证了调用f()g()的顺序,因此理论上第二个允许编译器略微更灵活。是否有所区别取决于f()g()实际执行的操作以及它们是否可以内联。


0

这两个示例之间存在轻微差异。在表达式f() + g()中,没有序列点,而当调用在不同语句中进行时,在每个语句的末尾都有序列点。

缺少序列点意味着调用这两个函数的顺序是未指定的,它们可以以任何顺序被调用,这可能有助于编译器进行优化。


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