Por que o gcc não pode inline pointers de function que podem ser determinados?

O seguinte programa compilado em gcc 4.6.2 em centos com -O3:

#include  #include  #include  #include  using namespace std; template  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 xv(N); auto begin = clock(); for_each (xv.begin(), xv.end(), F(&X::f)); auto end = clock(); cout << end - begin << endl; } 

objdump -D mostra que o código gerado para o loop é:

  40097c: e8 57 fe ff ff callq 4007d8  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  400994: 4c 39 e5 cmp %r12,%rbp 400997: 75 ef jne 400988  400999: e8 3a fe ff ff callq 4007d8  

Obviamente, o gcc não inline a function. Por que o gcc não é capaz dessa otimização? Existe algum sinalizador de compilador que pode fazer o gcc fazer a otimização desejada?

Algum bom material de leitura sobre isso é o Item 30 da Effective C ++ de Scott Adams Meyers (Terceira Edição): Entenda os meandros do inlining, onde ele afirma que uma chamada para o ponteiro de function nunca é embutida. A terceira edição foi publicada em 2008, e eu realmente consegui chamar o gcc para a chamada de function inline pelo ponteiro de constante de compilation a partir do gcc 4.6, que saiu em 2011 (talvez em 2010?). No entanto, isso foi em C e é complicado. Em um cenário, eu tive que declarar a function de chamada __attribute__((flatten)) antes que ela inlineizasse a chamada (nessa situação, eu passei o ponteiro de function como o membro de uma struct, que é o ponteiro passado para uma function inline que faria a chamada de function pelo ponteiro que foi inlined).

Então, em suma, não, isso não é um bug gcc, mas isso não significa que o gcc (e / ou outros compiladores) podem não ser capazes de embutir isso algum dia. Mas a questão real, eu acho, é que você não entende o que está realmente acontecendo aqui. Para obter essa compreensão, você precisa pensar mais como um programador de assembly ou um programador de compilador.

Você está passando um object do tipo F e inicializando-o com um ponteiro para uma function de membro de outra class. Você não declarou sua constante de object F instância, é Func f_ member como constante, nem seu membro void F::operator()(T& t) como constante. No nível da linguagem C ++, o compilador deve tratá-lo como não constante. Isso ainda não significa que não possa mais tarde, no estágio de otimização, determinar que o ponteiro de function não muda, mas você está tornando incrivelmente difícil neste momento. Mas pelo menos é um local. Se o seu object F fosse global e não fosse declarado static , ele o proibiria de ser considerado constante.

Espero que você esteja fazendo isso em um exercício de inlining por ponteiro de function e não como uma solução real para indireção. Quando você quer que o C ++ faça uma performance real, você usa o poder dos tipos. Especificamente, quando declaro um parâmetro de modelo como um ponteiro de function de membro, ele não é apenas uma constante, é parte do tipo. Eu nunca vi um caso em que esta técnica gera uma chamada de function.

 #include  #include  #include  #include  using namespace std; template  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 xv(N); auto begin = clock(); for_each (xv.begin(), xv.end(), F()); auto end = clock(); cout << end - begin << endl; } 

Eu acho que o GCC tenta otimizar toda a function main , mas falha (muitas chamadas indiretas de funções globais para alocar / liberar memory para xv , obter valor de timer, input / saída, etc). Então, você pode tentar dividir seu código em duas (ou mais) partes independentes, assim:

 inline void foobar(vector& xv) { for_each (xv.begin(), xv.end(), F(&X::f)); } int main() { const int N = 100000000; vector xv(N); auto begin = clock(); foobar(xv); auto end = clock(); cout << end - begin << endl; } 

Então, agora temos o código "equivalente" de antes, mas o otimizador do GCC tem uma tarefa mais fácil de fazer agora. Eu não vejo nenhuma chamada de ZN1X1fEv na lista de montadores agora.

Você pode adicionar inline __attribute__((__always_inline__)) à sua function e -Winline flag ao compilador, assim você será notado quando o compilador falhar na function inline.

Infelizmente, o atributo não fará com que sua function seja Winline , e o Winline não soará alarme. Até 4.8. MAS!!! De 4.9 esta questão parece ser corrigida!

Então, pegue seu gcc 4.9, adicione o sinalizador always_inline, defina o otimizador para -O3. E seja feliz!

Prova: http://goo.gl/kkuXzb