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

Pen*_*eng 25 c++ optimization

假设在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)

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

事实上,编译器被允许执行任何它想要的优化- 只要优化代码的行为完全一样的(考虑任何明显的影响)为完全未优化的一个.

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

  • 也许`f()`和`g()`正在使用一些全局变量,并且在第二次调用`f()`之前,第一次通过第一次`g()`调用修改了全局变量. (3认同)
  • 实际上编译器有时可以进行oprimization,因为它通常知道f和g的实现 (3认同)
  • @GauravSehgal:请注意,对于C++,它是未指定的(可能不一致),函数调用的顺序. (2认同)

Mat*_* M. 20

TL; DR:有函数调用minmax......


编译器可能会也可能不会为您执行此优化.

从编译器的角度来看,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)

但当然,更加光滑.

  • 请注意,Microsoft的<Windows.h>将`max`定义为宏,这使得`max(f(),g())`仍然调用f或g两次.`(std :: max)(f(),g())`是一种解决方法. (8认同)
  • Windows`#define max`是始终显式使用`std :: max`的另一个原因,因为至少你得到编译器错误而不是bug. (6认同)
  • @ jingyu9575:呃......我突然很高兴不在Windows上开发......我希望他们能以一种单一评估的方式编写宏... (3认同)
  • 有时看起来MS开发人员只是为了好玩而打破任何好习惯.我认为任何一家正常的公司都会解雇一个白痴,这个白痴会使用这样一个普通的名字制作宏小写. (2认同)

Ste*_*sop 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);
}
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)

因为它可以看到的定义fg,优化器还与他们的方式.