使用三元运算符(或类似)的中间变量来获得更好的性能?

时间:2016-06-14 07:34:43

标签: c++ optimization

假设在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()都不会被调用两次,或者编译器(给定足够的优化)会为我做这样的事情,所以我不需要做任何事情?

这个一般性问题当然也适用于许多类似的场景。

3 个答案:

答案 0 :(得分:35)

通常,不,编译器不会这样做 - 它实际上不可能。调用f和g可能会产生副作用,第二次调用f或g的结果可能与第一次调用时的结果不同。想象一下这样的事情:

int f()
{
    static int n = 0;
    return ++n;
}

但是有一些例外可以证明这一规则:

实际上,只要优化代码的行为完全相同(考虑任何可见),就允许编译器执行它想要的任何优化。 em> effects)作为完全未被优化的人。

因此,如果编译器可以保证省略第二个函数调用不会抑制任何副作用(并且只有那么!),它实际上可以优化第二个调用,并且很可能也会在更高优化时水平。

答案 1 :(得分:20)

TL; DR :有一些名为maxf() > 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)

"给予足够的优化"编译器可能执行此操作,具体取决于函数fg的特征。如果编译器可以看到函数的定义(因此他们要么在同一个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

由于它可以看到fg的定义,因此优化程序可以使用它们。