为什么 gcc 不能确定可以确定的内联函数指针?
Why gcc can't inline function pointers that can be determined?
以下程序在带有-O3的centos上根据gcc 4.6.2编译:
#include <iostream>
#include <vector>
#include <algorithm>
#include <ctime>
using namespace std;
template <typename T>
class F {
public:
typedef void (T::*Func)();
F(Func f) : f_(f) {}
void operator()(T& t) {
(t.*f_)();
}
private:
Func f_;
};
struct X {
X() : x_(0) {}
void f(){
++x_;
}
int x_;
};
int main()
{
const int N = 100000000;
vector<X> xv(N);
auto begin = clock();
for_each (xv.begin(), xv.end(), F<X>(&X::f));
auto end = clock();
cout << end - begin << endl;
}
objdump -D
显示为循环生成的代码为:
40097c: e8 57 fe ff ff callq 4007d8 <clock@plt>
400981: 49 89 c5 mov %rax,%r13
400984: 0f 1f 40 00 nopl 0x0(%rax)
400988: 48 89 ef mov %rbp,%rdi
40098b: 48 83 c5 04 add $0x4,%rbp
40098f: e8 8c ff ff ff callq 400920 <_ZN1X1fEv>
400994: 4c 39 e5 cmp %r12,%rbp
400997: 75 ef jne 400988 <main+0x48>
400999: e8 3a fe ff ff callq 4007d8 <clock@plt>
显然,gcc没有内联函数。为什么gcc不能进行这种优化?是否有任何编译器标志可以使gcc进行所需的优化?
我认为GCC试图优化整个main
函数,但失败了(大量间接调用全局函数为xv
分配/释放内存,获取定时器值、输入/输出等)。因此,您可以尝试将代码拆分为两个(或更多)独立的部分,如下所示:
inline
void foobar(vector<X>& xv)
{
for_each (xv.begin(), xv.end(), F<X>(&X::f));
}
int main()
{
const int N = 100000000;
vector<X> xv(N);
auto begin = clock();
foobar(xv);
auto end = clock();
cout << end - begin << endl;
}
所以,现在我们有了和以前一样"等价"的代码,但GCC的优化器现在有了更容易完成的任务。我现在没有在汇编程序列表中看到任何对ZN1X1fEv
的调用。
关于这方面的一些好的阅读材料是ScottAdamsMeyers的Effective C++(第三版)第30项:理解内联的来龙去脉,他声称对函数指针的调用永远不会内联。第三版发布于2008年,我确实能够从2011年(可能是2010年)发布的gcc 4.6开始,通过编译时常量指针来实现gcc到内联函数的调用。然而,这是用C编写的,非常棘手。在一个场景中,我必须在调用函数__attribute__((flatten))
内联调用之前声明它(在这种情况下,我将函数指针作为结构的成员传递,然后我将其指针传递给内联函数,该函数将通过内联的指针进行函数调用)。
简而言之,不,这不是gcc的bug,但这并不意味着gcc(和/或其他编译器)有朝一日可能无法内联它。但我认为,真正的问题是,你不了解这里到底发生了什么。为了理解这一点,你必须像一个汇编程序员或编译器程序员一样思考。
您正在传递一个类型为F<X>
的对象,并使用指向另一个类的成员函数的指针对其进行初始化。您还没有将实例F<X>
对象声明为常量,它是Func f_
成员,也没有将void F::operator()(T& t)
成员声明为常量。在C++语言级别,编译器必须将其视为非常量。这仍然不意味着它以后在优化阶段不能确定你的函数指针没有改变,但你在这一点上让它变得非常困难。但至少它是本地的。如果您的F<X>
对象是全局的,并且没有声明为static
,那么它将完全禁止将其视为常量。
希望您这样做是在函数指针内联的练习中,而不是作为间接寻址的真正解决方案。当你想让C++产生真正的性能时,你需要使用类型的力量。具体来说,当我将模板参数声明为成员函数指针时,它不仅仅是一个常量,它也是类型的一部分。我从未见过这种技术生成函数调用的情况。
#include <iostream>
#include <vector>
#include <algorithm>
#include <ctime>
using namespace std;
template <typename T, void (T::*f_)()>
class F {
public:
void operator()(T& t) {
(t.*f_)();
}
};
struct X {
X() : x_(0) {}
void f(){
++x_;
}
int x_;
};
int __attribute__((flatten)) main()
{
const int N = 100000000;
vector<X> xv(N);
auto begin = clock();
for_each (xv.begin(), xv.end(), F<X, &X::f>());
auto end = clock();
cout << end - begin << endl;
}
您可以将inline __attribute__((__always_inline__))
添加到函数中,并将-Winline
标志添加到编译器中,因此当编译器无法内联函数时,您会注意到这一点。
不幸的是,属性不会使函数内联,并且Winline
不会发出警报。取消键入4.8。但是!!!从4.9开始,这个问题似乎已经解决了!
所以,获取您的gcc 4.9,添加always_inline标志,将优化器设置为-O3级别。快乐吧!
证明:http://goo.gl/kkuXzb
- QMetaObject invokeMethod的基于函数指针的语法
- C++-试图将函数指针推回到另一个CPP文件中的矢量时出错
- c++r值引用应用于函数指针
- 模板函数指针和lambda
- 是否可以将llvm::FunctionType转换为C/C++原始函数指针
- 带有类的函数指针
- () 函子后面的括号,而不是函数指针?
- 全局作用域中函数指针的赋值
- 使用"Task"函数指针队列定义作业管理器
- 将成员函数指针作为参数传递给模板方法
- 如何创建对象函数指针C++映射?
- 匹配函数指针作为模板参数?
- 通过函数指针定义类范围之外的方法
- 存储在类中的函数指针
- C++从函数指针数组调用函数
- 将返回值存储在函数指针数组的指针中是如何工作的?
- 整数键映射到头文件中的成员函数指针
- 从类成员函数到类 C 函数指针的转换
- 如何将内联匿名函数分配给C++函数指针
- 将字符缓冲区强制转换为函数指针