以下程序在带有-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;
}
Run Code Online (Sandbox Code Playgroud)
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>
Run Code Online (Sandbox Code Playgroud)
显然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;
}
Run Code Online (Sandbox Code Playgroud)
所以,现在我们有像以前一样的"等效"代码,但GCC的优化器现在更容易完成任务.我现在没有看到ZN1X1fEv汇编列表的任何调用.
一些好的阅读材料是Scott Adams Meyers的Effective C++(第三版)第30项:理解内联的细节,他声称函数指针的调用永远不会内联.第三版是在2008年发布的,我确实能够通过编译时间常量指针获得gcc到内联函数调用,这些指针始于2011年出现的gcc 4.6(可能是2010年?).但是,这是在C中并且很棘手.在一个场景中,我必须先声明调用函数__attribute__((flatten))才能内联调用(在这种情况下,我将函数指针作为结构的成员传递,然后将指针传递给内联函数,该函数将调用函数内联的指针).
所以简而言之,不,这不是一个bug gcc,但这并不意味着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;
}
Run Code Online (Sandbox Code Playgroud)