假设在C ++(或C,Java等)中我有这样的代码:
int a = f() > g() ? f() : g();
当然在 a 中指定f()和g()的返回值之间的较大值。现在假设f()和g()本身是复杂而缓慢的,我应该用
之类的东西替换这一行int f_value = f();
int g_value = g();
int a = f_value > g_value ? f_value : g_value;
所以f()和g()都不会被调用两次,或者编译器(给定足够的优化)会为我做这样的事情,所以我不需要做任何事情?
这个一般性问题当然也适用于许多类似的场景。
答案 0 :(得分:35)
通常,不,编译器不会这样做 - 它实际上不可能。调用f和g可能会产生副作用,第二次调用f或g的结果可能与第一次调用时的结果不同。想象一下这样的事情:
int f()
{
static int n = 0;
return ++n;
}
但是有一些例外可以证明这一规则:
实际上,只要优化代码的行为完全相同(考虑任何可见),就允许编译器执行它想要的任何优化。 em> effects)作为完全未被优化的人。
因此,如果编译器可以保证省略第二个函数调用不会抑制任何副作用(并且只有那么!),它实际上可以优化第二个调用,并且很可能也会在更高优化时水平。
答案 1 :(得分:20)
TL; DR :有一些名为max
和f() > g() ? f() : g()
的函数......
编译器可能会也可能不会为您执行此优化。
从编译器的角度来看,entry:
_0 = f();
_1 = g();
_cmp = _0 > _1
if _cmp: goto _greater; else: goto _lesser;
greater:
_2 = f();
goto end;
lesser:
_3 = g();
goto end;
end:
phi [greater _2], [lesser _3]
可能是:
f()
这称为SSA表单(静态单一分配表单),并且由大多数优化器(如LLVM和gcc)使用。
编译器评估g()
或f()
一次或两次取决于是否:
g()
和pure
注释为pure
或评估为f()
(无副作用,仅取决于输入)g()
和max
在通话方面内联一般来说,我不会指望它。
然而,所有这一切并不重要。
有更高级别的功能可以执行您想要的操作,例如int a = std::max(f(), g());
:
f()
保证,在C ++中,它只会评估g()
和max
一次(评估顺序无法保证,但两者都只会被评估一次,并且在调用{{}之前1}}本身)。
这完全等同于:
int _0 = f();
int _1 = g();
int a = std::max(_0, _1);
但当然,更加光滑。
答案 2 :(得分:9)
"给予足够的优化"编译器可能执行此操作,具体取决于函数f
和g
的特征。如果编译器可以看到函数的定义(因此他们要么在同一个TU中调用它们,要么您正在使用链接时优化),并且可以看到它们没有 - 效果及其结果不依赖于任何全局变量,那么它只能评估一次而不是两次。
如果他们确实有副作用,那么你要求他们被叫两次,所以其中一个会被评估两次。
如果他们重新constexpr
,则无法再打电话给他们。
对于您的示例,使用std::max(f(), g())
通常比使用中间变量更方便。像任何函数调用一样,它只评估每个参数一次。
鉴于此代码:
int f(int x) {
return x + 1;
}
int g(int x) {
return x + 2;
}
int foo(int a, int b) {
return f(a) > g(b) ? f(a) : g(b);
}
我机器上的 gcc -O0产生以下内容。即使您无法阅读,也请注意callq <_Z1fi>
出现两次:
int foo(int a, int b) {
1e: 55 push %rbp
1f: 53 push %rbx
20: 48 83 ec 28 sub $0x28,%rsp
24: 48 8d ac 24 80 00 00 lea 0x80(%rsp),%rbp
2b: 00
2c: 89 4d c0 mov %ecx,-0x40(%rbp)
2f: 89 55 c8 mov %edx,-0x38(%rbp)
return f(a) > g(b) ? f(a) : g(b);
32: 8b 4d c0 mov -0x40(%rbp),%ecx
35: e8 c6 ff ff ff callq 0 <_Z1fi>
3a: 89 c3 mov %eax,%ebx
3c: 8b 45 c8 mov -0x38(%rbp),%eax
3f: 89 c1 mov %eax,%ecx
41: e8 c9 ff ff ff callq f <_Z1gi>
46: 39 c3 cmp %eax,%ebx
48: 7e 0a jle 54 <_Z3fooii+0x36>
4a: 8b 4d c0 mov -0x40(%rbp),%ecx
4d: e8 ae ff ff ff callq 0 <_Z1fi>
52: eb 0a jmp 5e <_Z3fooii+0x40>
54: 8b 45 c8 mov -0x38(%rbp),%eax
57: 89 c1 mov %eax,%ecx
59: e8 b1 ff ff ff callq f <_Z1gi>
}
5e: 48 83 c4 28 add $0x28,%rsp
62: 5b pop %rbx
63: 5d pop %rbp
64: c3 retq
而gcc -O2产生:
int foo(int a, int b) {
return f(a) > g(b) ? f(a) : g(b);
20: 8d 42 02 lea 0x2(%rdx),%eax
23: 83 c1 01 add $0x1,%ecx
26: 39 c1 cmp %eax,%ecx
28: 0f 4d c1 cmovge %ecx,%eax
}
2b: c3 retq
由于它可以看到f
和g
的定义,因此优化程序可以使用它们。