为什么gcc不能内联可以确定的函数指针?

Kan*_* Li 8 c++ gcc

以下程序在带有-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进行所需的优化?

qeh*_*hgt 7

我认为,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汇编列表的任何调用.


Dan*_*tos 7

一些好的阅读材料是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)

  • 在我的例子中,编译器已经知道它应该调用_ZN1X1fEv(X :: f),以便它直接生成程序集`callq ZN1X1fEv`而不是通过指针调用.显然你只记得一些规则(函数指针不能内联),但不明白为什么.如果编译器可以在编译时确定其值,则没有理由不能内联.另外你的例子是完全无关的,从逻辑上讲,它显示了gcc工作的情况,但我问为什么它对我的情况不起作用. (2认同)
  • @DanielSantos,正如我所说的那样,我知道C++编译器可以忽略`inline`关键字.我最初的问题是为什么优化器无法内联它,即使它可以通过静态分析告诉函数指针的确切值(因为它直接生成正确的函数调用而不是间接生成). (2认同)
  • "内联"意味着不将某些内容替换为直接调用,而是将某些函数的内容移动到您调用它的位置,因此您根本不需要堆栈框架和函数调用(传递参数,等等) .所以,我不明白如何将一些参数调用替换为直接函数调用意味着"内联".@KanLi你觉得我错了吗? (2认同)