假设我们有两个函数:
int f();
int g();
我希望得到f()和g()的总和。
第一种方式:
int fRes = f();
int gRes = g();
int sum = fRes + gRes;
第二种方法:
int sum = f() + g();
这两种情况的性能有什么区别吗?
对于复杂类型而不是整数,同样的问题。
编辑
我理解得对吗?在这种情况下(包括频繁执行的任务),我不必担心性能,并使用临时变量来增加可读性和简化代码?
假设我们有两个函数:
int f();
int g();
我希望得到f()和g()的总和。
第一种方式:
int fRes = f();
int gRes = g();
int sum = fRes + gRes;
第二种方法:
int sum = f() + g();
这两种情况的性能有什么区别吗?
对于复杂类型而不是整数,同样的问题。
编辑
我理解得对吗?在这种情况下(包括频繁执行的任务),我不必担心性能,并使用临时变量来增加可读性和简化代码?
你可以通过编译成汇编语言(当然要启用优化)并检查输出来自己回答这样的问题。如果我将你的示例完整地扩展为可编译程序...
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
指令数量大大增加了,编译器似乎没有进行完美的优化工作,但是两个函数生成的代码是相同的。
-O0
而不是-O2
,我会预计会有所不同。但是,将它们生成等效的代码是书中最基本的优化之一(通常称为“前向传播”),当启用优化时,现代编译器不执行此操作是不适合的。 - zwol我认为第二种方式是在函数返回值时将其分配给一个临时变量。然而,当您需要多次使用f()
和g()
的值时,将它们存储到变量中而不是每次重新计算可能会更有帮助。
根据你所写的内容,它们之间只有微小的差异(另一个答案已经解决了这个问题,其中一个具有序列点)。
如果你改为以下方式,则它们将不同:
int fRes;
int gRes;
fRes = f();
fRes = g();
int sum = fRes + gRes;
int
实际上是具有非平凡默认构造函数的其他类型。)如果你关闭了优化,可能会有差异。如果开启了优化,它们很可能会导致相同的代码。特别是当您将fRes
和gRes
标记为const
时,这一点尤为明显。
因为编���器可以省略对复制构造函数的调用,如果fRes
和gRes
是复杂类型,它们在性能上也不会有区别。
有人提到过多次使用fRes
和gRes
。当然,这很明显可能不太优化,因为您必须多次调用f()
或g()
。
这完全取决于编译器进行了哪些优化。这两者可能会编译成略有不同或完全相同的字节码。即使稍有不同,对于这些特定样本,您也无法测量出统计上显著的时间和空间成本差异。
在我的平台上,开启了完全优化的情况下,一个返回两个不同情况下sum
的函数编译成了完全相同的机器码。
这两个例子之间唯一的小差别是第一个保证了调用f()
和g()
的顺序,因此理论上第二个允许编译器略微更灵活。是否有所区别取决于f()
和g()
实际执行的操作以及它们是否可以内联。
这两个示例之间存在轻微差异。在表达式f() + g()
中,没有序列点,而当调用在不同语句中进行时,在每个语句的末尾都有序列点。
缺少序列点意味着调用这两个函数的顺序是未指定的,它们可以以任何顺序被调用,这可能有助于编译器进行优化。