Почему gcc не может определить встроенные указатели функций?

Следующая программа, скомпилированная в gcc 4.6.2 на centos с -O3:

#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 выполнить желаемую оптимизацию?

3 ответа

Я думаю, 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 в списке ассемблера сейчас.

Хороший материал для чтения по этому вопросу - "Эффективный язык С ++" (третье издание) Скотта Адамса Майерса. Пункт 30: Понимать все входы и выходы встраивания, где он утверждает, что вызов указателя на функцию никогда не бывает встроенным. Третье издание было опубликовано в 2008 году, и я действительно смог получить gcc для вызова встроенных функций с помощью указателя времени компиляции, начиная с gcc 4.6, который вышел в 2011 году (может быть, в 2010 году). Тем не менее, это было в 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;

}

Вы могли бы добавить inline __attribute__((__always_inline__)) к вашей функции, и -Winline флаг компилятору, так что вы заметите, когда компилятор не сможет встроить функцию.

К сожалению, атрибут не сделает вашу функцию встроенной, и Winline не звучит тревога. До 4.8. НО!!! С 4.9 эта проблема, похоже, исправлена!

Итак, возьмите ваш gcc 4.9, добавьте флаг always_inline, установите оптимизатор на уровень -O3. И будь счастлив!

Доказательство: http://goo.gl/kkuXzb

Другие вопросы по тегам