C++ 编译器会优化重复的函数调用吗?

SU3*_*SU3 8 c++ compiler-optimization

编译器(通常或特别)是否优化重复的函数调用?

例如,考虑这种情况。

struct foo {
  member_type m;
  return_type f() const; // returns by value
};
Run Code Online (Sandbox Code Playgroud)

函数定义在一个翻译单元中

return_type foo::f() const {
  /* do some computation using the value of m */
  /* return by value */
}
Run Code Online (Sandbox Code Playgroud)

重复的函数调用在另一个单元中

foo bar;

some_other_function_a(bar.f());
some_other_function_b(bar.f());
Run Code Online (Sandbox Code Playgroud)

第二个翻译单元中的代码会转换成这个吗?

foo bar;

const return_type _tmp_bar_f = bar.f();

some_other_function_a(_tmp_bar_f);
some_other_function_b(_tmp_bar_f);
Run Code Online (Sandbox Code Playgroud)

潜在地,计算f确实可能很昂贵,但返回的类型可能非常小(考虑一个返回 a 的数学函数double)。编译器会这样做吗?有没有他们做或不做的情况?您可以考虑这个问题的广义版本,而不仅仅是成员函数或没有参数的函数。

根据@BaummitAugen 的建议澄清

我更感兴趣的是这里问题的理论方面,而不是人们是否可以依靠它来使现实世界的代码运行得更快。我对 Linux 上 x86_64 上的 GCC 尤其感兴趣。

Nic*_*son 7

如果您启用了链接时间优化并且优化级别足够高,GCC 绝对可以跨编译单元进行优化,请参见此处:https : //gcc.gnu.org/wiki/LinkTimeOptimization除了编译时间之外,没有理由不同时执行这两项操作这些。

此外,您始终可以通过使用适当的属性标记函数来帮助编译器。您可能希望使用属性 const 标记函数,如下所示:

struct foo {
  member_type m;
  return_type f() const __attribute__((const)); // returns by value
};
Run Code Online (Sandbox Code Playgroud)

看看这里的 GCC 文档,看看哪个属性是合适的:https : //gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html

从更一般的意义上讲,这对于编译器来说很容易检测到。它实际上执行了不太明显的转换。然而,链接时间优化之所以重要,是因为一旦 GCC 生成了实际的机器代码,它就不会真正知道此时做什么是安全的。例如,您的函数可以修改数据(在您的类之外)或访问 volatile 变量。

编辑:

GCC 绝对可以做这个优化。使用此代码和标志 -O3 -fno-inline:

C++代码:

#include <iostream>

int function(int c){
  for(int i = 0; i != c; ++i){
    c += i;
  }
  return c;
}

int main(){
  char c;
  ::std::cin >> c;
  return function(c) + function(c) + function(c) + function(c) + function(c);
}
Run Code Online (Sandbox Code Playgroud)

组装输出:

4006a0: 48 83 ec 18             sub    rsp,0x18
4006a4: bf 80 0c 60 00          mov    edi,0x600c80
4006a9: 48 8d 74 24 0f          lea    rsi,[rsp+0xf]
4006ae: e8 ad ff ff ff          call   400660 <_ZStrsIcSt11char_traitsIcEERSt13basic_istreamIT_T0_ES6_RS3_@plt>
4006b3: 0f b6 7c 24 0f          movzx  edi,BYTE PTR [rsp+0xf]
4006b8: e8 13 01 00 00          call   4007d0 <_Z8functioni>
4006bd: 48 83 c4 18             add    rsp,0x18
4006c1: 8d 04 80                lea    eax,[rax+rax*4]
4006c4: c3                      ret    
4006c5: 66 66 2e 0f 1f 84 00    data32 nop WORD PTR cs:[rax+rax*1+0x0]
4006cc: 00 00 00 00 
Run Code Online (Sandbox Code Playgroud)

但是,当函数位于单独的编译单元中并且未指定 -flto 选项时,它无法执行此操作。只是为了澄清,这一行调用了函数:

call   4007d0 <_Z8functioni>
Run Code Online (Sandbox Code Playgroud)

此行将结果乘以 5(将五个副本相加):

lea    eax,[rax+rax*4]
Run Code Online (Sandbox Code Playgroud)