10

以下程序在 gcc 4.6.2 下使用 -O3 在 centos 上编译:

#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 进行所需的优化?

4

3 回答 3

8

我认为,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现在在汇编列表中看不到任何调用。

于 2012-04-16T15:41:12.440 回答
7

一些很好的阅读材料是 Scott Adams Meyers 的 Effective C++(第三版)第 30 条:了解内联的来龙去脉,他声称对函数指针的调用永远不会内联。第三版于 2008 年发布,我确实能够让 gcc 从 2011 年(可能是 2010 年?)发布的 gcc 4.6 开始通过 compile-time-constant-pointer 进行内联函数调用。但是,这是在 C 中的,并且很棘手。在一种情况下,我必须在调用函数__attribute__((flatten))内联调用之前声明调用函数(在这种情况下,我将函数指针作为结构的成员传递,然后我将谁的指针传递给一个内联函数,该函数通过内联的指针)。

所以简而言之,不,这不是 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;

}
于 2012-06-17T22:49:23.650 回答
1

您可以添加inline __attribute__((__always_inline__))到您的函数中,并-Winline标记到编译器,因此当编译器无法内联函数时您会注意到。

不幸的是,属性不会使您的函数内联,也Winline不会发出警报。直到 4.8。但!!!从 4.9 开始,此问题似乎已修复!

所以,抓住你的 gcc 4.9,添加 always_inline 标志,将优化器设置为 -O3 级别。而且要快乐!

证明:http: //goo.gl/kkuXzb

于 2014-07-17T02:58:41.180 回答