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下x8_64上的GCC特别感兴趣。
GCC绝对可以跨编译单元进行优化,如果您启用了链接时间优化,并且优化级别足够高,请参阅此处:https://gcc.gnu.org/wiki/LinkTimeOptimization除了编译时间之外,真的没有理由不同时做这两件事。
此外,您还可以通过用适当的属性标记函数来帮助编译器。您可能想用属性const标记函数,如下所示:
struct foo {
member_type m;
return_type f() const __attribute__((const)); // returns by value
};
请查看此处的GC文档,了解哪种属性是合适的:https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html
从更普遍的意义上讲,这对编译器来说非常容易检测到。它实际上执行的转换不那么明显。然而,链接时间优化之所以重要,是因为一旦GCC生成了实际的机器代码,它就不知道在这一点上做什么是安全的。例如,您的函数可以修改数据(在类之外)或访问易失性变量。
编辑:
GCC绝对可以做到这一优化。有了这个代码和标志-O3-fno内联:
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可以通过内联引号来优化这一点:"simple enough"函数)。如果您希望编译器能够优化调用,它需要知道test
和some
都是纯的,即为more(test())
缩放test
不可能返回与为some(test())
调用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。
- 函数调用中参数的顺序重要吗
- 基于另一个成员参数将函数调用从类传递给它的一个成员
- 变量没有改变?通过向量的函数调用
- 在两个类中共享相同的函数调用,并在不需要时避免空实例化
- 是否有C++编译器选项允许激进地删除所有函数调用,并将参数传递给具有空体的函数
- 我知道函数调用中存在歧义.有没有办法调用foo()函数
- 模板函数调用
- 编译器 虚拟函数调用的优化
- 根据全局日志级别优化日志函数调用
- C++:优化析构函数调用
- C++编译器是否优化重复的函数调用
- 编译时,复制构造函数/复制分配和正常功能调用优化之间是否存在任何区别
- 下面的代码会被优化为一个函数调用吗
- C/C++中使用常量优化的函数调用
- 优化外函数调用
- 为什么全局变量会给函数调用中的编译器优化带来麻烦
- 返回值优化和析构函数调用
- gcc是否会优化对相同变量的重复函数调用,并为每次调用提供相同的输出?
- 派生类中虚函数调用的优化
- 使用返回值优化get和函数调用周围的循环