假设在C++(或C,Java等)中我有这样的代码:
int a = f() > g() ? f() : g();
Run Code Online (Sandbox Code Playgroud)
这当然分配一个 F()和g的返回值之间具有较大().现在假设f()和g()本身是复杂而缓慢的,我应该用这样的东西替换这一行
int f_value = f();
int g_value = g();
int a = f_value > g_value ? f_value : g_value;
Run Code Online (Sandbox Code Playgroud)
所以既不F()和G()将被调用两次,或者是编译器(给予足够的优化)会为我做这样的事情呢,所以我没有做任何事情?
当然,这个一般性问题也适用于许多类似的情况.
Aco*_*gua 35
通常,不,编译器不会这样做 - 实际上它不能.调用f和g可能会产生副作用,第二次调用f或g的结果可能与第一次调用时的结果不同.想象一下这样的事情:
int f()
{
static int n = 0;
return ++n;
}
Run Code Online (Sandbox Code Playgroud)
但是有一些例外证明了这一规则:
事实上,编译器被允许执行任何它想要的优化- 只要优化代码的行为完全一样的(考虑任何明显的影响)为完全未优化的一个.
因此,如果编译器可以保证省略第二个函数调用不会抑制任何副作用(并且只有那么!),它实际上允许优化第二个调用,并且在更高的优化级别也很可能也是如此.
Mat*_* M. 20
TL; DR:有函数调用min
和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]
Run Code Online (Sandbox Code Playgroud)
这称为SSA表单(静态单一分配表单),并由大多数优化器(如LLVM和gcc)使用.
编译器是评估f()
还是g()
一次或两次取决于是否:
f()
并且g()
注释为pure
或评估为pure
(无副作用,仅取决于输入)f()
与g()
在呼叫侧的内联一般来说,我不会指望它.
但是,所有这一切并不重要.
有更高级别的功能可以做你想要的,例如max
:
int a = std::max(f(), g());
Run Code Online (Sandbox Code Playgroud)
担保,在C++中,它仅会评估f()
和g()
一次(评价的顺序是没有保证的,但都将只计算一次,并调用之前max
本身).
这完全等同于:
int _0 = f();
int _1 = g();
int a = std::max(_0, _1);
Run Code Online (Sandbox Code Playgroud)
但当然,更加光滑.
"给定足够的优化"编译器可能会这样做,具体取决于函数的特性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);
}
Run Code Online (Sandbox Code Playgroud)
我的机器上的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
Run Code Online (Sandbox Code Playgroud)
而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
Run Code Online (Sandbox Code Playgroud)
因为它可以看到的定义f
和g
,优化器还与他们的方式.
归档时间: |
|
查看次数: |
1685 次 |
最近记录: |