繁体   English   中英

C ++编译器是否优化了重复的函数调用?

[英]Do C++ compilers optimize repeated function calls?

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

例如,考虑这种情况。

struct foo {
  member_type m;
  return_type f() const; // returns by value
};

功能定义在一个翻译单元中

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

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

foo bar;

some_other_function_a(bar.f());
some_other_function_b(bar.f());

第二个翻译单元中的代码是否会转换为此代码?

foo bar;

const return_type _tmp_bar_f = bar.f();

some_other_function_a(_tmp_bar_f);
some_other_function_b(_tmp_bar_f);

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

根据@ BaummitAugen的建议澄清

我对这个问题的理论方面更感兴趣,而不是更依赖于是否可以依赖于此来使现实世界的代码运行得更快。 我对使用Linux的x86_64上的GCC特别感兴趣。

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

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

struct foo {
  member_type m;
  return_type f() const __attribute__((const)); // returns by value
};

在这里查看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);
}

装配输出:

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 

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

call   4007d0 <_Z8functioni>

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

lea    eax,[rax+rax*4]

编译器无法查看编译单元,因此无法在调用站点告知调用是否存在副作用,因此优化它是不正确的。

除非函数的第一次和最后一次调用之间的函数和所有函数都被声明为纯(即没有任何副作用),否则编译器无法优化调用。 请注意以下事项:

int test();
void some(int a);
void more(int b);

int main()
{
    some(test());
    more(test());
}

在这里, test可能会被调用两次,因为它可以返回不同的值(LTO可以通过内联引用来优化它: “足够简单”的函数 )。 如果你想编译器能够优化呼叫,它需要知道这两个testsome是纯的,即caling testmore(test())不可能返回调用时不同值test对于some(test()) 因此,可以优化以下(并将在GCC和Clang中)对单个调用进行test

int test() __attribute__ ((pure));
void some(int a) __attribute__ ((pure));
void more(int b);

int main()
{
    some(test());
    more(test());
}

(请注意, more不需要纯粹。)

不幸的是,还没有任何标准方法将函数声明为纯函数,上面是非标准的GCC扩展。 建议N3744[[pure]]添加到ISO C ++中(对纯度有更强的保证, some不需要纯粹的)但我不知道它是否会使它成为C ++ 17 。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM