Use std :: function para todas as suas necessidades de passagem de função.

Mas não se esqueça de ativar as otimizações do compilador! Ou use o suporte lambda nativo do C ++ 11.

Ao arquitetar um sistema de arquivos do espaço do usuário que será executado no Mac OS (usando libfuse ) e Windows (usando CBFS ), eu queria configurar uma maneira fácil de vincular os callbacks do sistema de arquivos (ou seja, createFile, openFile, readFile, writeFile, etc). Meu primeiro pensamento foi “Ei, vamos usar o incrível std::functione do C ++ 11. std::bindIsso me permitiria passar facilmente métodos de retorno de chamada de classes arbitrárias.

Meu segundo pensamento foi “Ei, adicionar camadas extras entre o receptor e o chamador em cada acesso ao sistema de arquivos não seria lento?” Então decidi testar e ver. Você pode encontrar o código nesta essência . Minha estrutura de teste é bastante simples: em um loop, chame a função 1 bilhão de vezes e veja quanto tempo leva. Fiz isso com 4 tipos diferentes de funções, 4 maneiras diferentes de executar a função e 2 níveis diferentes de otimização do compilador.

A função executou alguma aritmética que não é muito fácil para o compilador otimizar completamente (principalmente a raiz quadrada).

int func( int a, int b ){
int c = a * b;
c
*= a;
double root = std::sqrt( (double)c );
return (int)root;
}

O loop era assim:

const int INTERATIONS = 1000000000; // 1 billion
for( int i = 0; i < ITERATIONS; ++i ){
func
( i, ITERATIONS - 1 );
}

Os quatro tipos de função diferentes eram:

  1. Função embutida do arquivo de cabeçalho.
  2. Função externa de outra unidade de compilação.
  3. Função de membro de classe embutida do arquivo de cabeçalho.
  4. Função de membro de classe externa de outra unidade de compilação.

Os quatro métodos diferentes de chamada foram:

  1. Chame a função diretamente.
  2. Chame a função por meio de um ponteiro para ela.
  3. Envolva a função e chame-a.std::function
  4. Envolva a função em um lambda e chame-o.

Os dois níveis de otimização diferentes foram:

  1. -O0 Sem otimizações.
  2. -O3 Todas as otimizações não experimentais.

Os resultados foram um pouco inesperados, mas muito bons no caso otimizado. Esses testes foram executados no meu MacBook Pro emitido pelo trabalho executando OS X 10.9 com um Intel Core i7 de 2,3 GHz e 8 GiB de RAM DDR3. Eles foram compilados com g ++ versão 4.7.1 de MacPorts.

natalie@WorkBook:funcSpeed$ g++ -std=c++11 test.cpp main.cpp -o funcSpeed && ./funcSpeed
--- Direct Call Tests ---
testInline
9317ms.
testExternal
9433ms.
tester
.testInlineMember 9252ms.
tester
.testExternalMember 9328ms.
--- Pointer Call Tests ---
(&testInline) 9401ms.
(&testExternal) 9642ms.
(tester.*(&Test::testInlineMember)) 9515ms.
(tester.*(&Test::testExternalMember)) 9382ms.
--- std::function Call Tests ---
funcTestInline
134101ms.
funcTestExternal
134797ms.
funcTestInlineMember
153735ms.
funcTestExternalMember
154390ms.
--- Lambda Call Tests ---
[&]( int a, int b ){ testInline( a, b ); } 10586ms.
[&]( int a, int b ){ testExternal( a, b ); } 10441ms.
[&]( int a, int b ){ tester.testInlineMember( a, b ); } 10996ms.
[&]( int a, int b ){ tester.testExternalMember( a, b ); } 11231ms.
natalie@WorkBook
:funcSpeed$ g++ -std=c++11 -O3 test.cpp main.cpp -o funcSpeed && ./funcSpeed # Optimized!
--- Direct Call Tests ---
testInline
7693ms.
testExternal
8297ms.
tester
.testInlineMember 7790ms.
tester
.testExternalMember 7986ms.
--- Pointer Call Tests ---
(&testInline) 7617ms.
(&testExternal) 8031ms.
(tester.*(&Test::testInlineMember)) 7752ms.
(tester.*(&Test::testExternalMember)) 8119ms.
--- std::function Call Tests ---
funcTestInline
9866ms.
funcTestExternal
10128ms.
funcTestInlineMember
8346ms.
funcTestExternalMember
8331ms.
--- Lambda Call Tests ---
[&]( int a, int b ){ testInline( a, b ); } 7793ms.
[&]( int a, int b ){ testExternal( a, b ); } 8211ms.
[&]( int a, int b ){ tester.testInlineMember( a, b ); } 7957ms.
[&]( int a, int b ){ tester.testExternalMember( a, b ); } 7920ms.

O primeiro conjunto é o caso não otimizado. O caso de chamada direta não otimizada foi o que eu esperava, todas levam ao mesmo tempo, pois, sem otimizações ativadas, a testInlinefunção não é embutida. O mesmo para as chamadas baseadas em ponteiro. Uma pequena surpresa foi que o teste de ponteiro para membro externo sempre executou 200 – 300 milissegundos mais rápido do que o teste de ponteiro para membro embutido no código não otimizado (uma diferença sem importância, já que estamos falando de uma diferença de 200 -300 nanossegundos por chamada).

O primeiro ponto real de interesse vem com os testes. A combinação de e aumenta o tempo de execução em ~ 16x para funções de membro e ~ 14x para funções de não membro. Muito desse overhead está vindo . A execução de testes de não membros apenas me deu resultados de 33693ms e 33591ms para os testes internos e externos, respectivamente. Isso é apenas ~ 3,6x multiplicador. Este wrapper está adicionando 143.000 nanossegundos extras (0,143 milissegundos) para cada chamada. Embora isso ainda seja consideravelmente menos do que qualquer coisa perceptível por um humano, obviamente acrescenta.std::functionstd::functionstd::bindstd::bindstd::function

As funções lambda, no entanto, são consideravelmente mais rápidas, o que me surpreendeu. Sempre presumi que, por baixo do capô, o compilador estava apenas traduzindo os lambdas em objetos, mas claramente não é o caso. Aqui, a diferença entre a chamada direta e a chamada encapsulada em lambda é de apenas 1.200 nanossegundos (0,0012 milissegundos) por chamada. Uma sobrecarga decente, mas nada mal considerando a produtividade do programador e os ganhos de manutenção do projeto desses recursos.std::function

As coisas podem parecer sombrias , mas as otimizações do compilador chegam para salvar o dia. Primeiro, vemos que, quando o sinalizador é definido (está implicitamente definido com ), as funções inline são um clipe decente mais rápido do que as externas. Nenhuma grande surpresa nisso. O teste de chamada de ponteiro também se parece muito com o teste de chamada direta, provavelmente porque o compilador está otimizando minha desreferenciação, mas isso não é importante para esta discussão.std::bind-finline-functions-O3

Mais interessante, vemos que a combinação e otimiza lindamente. Agora estamos reduzidos a um multiplicador de ~ 1,08xe ~ 1,23x para as funções membro e não membro, respectivamente. Parece que com as otimizações habilitadas, o / combo tem um desempenho melhor para funções de membro do que para não membros. Também curiosamente, o desempenho otimizado de não membros é o mesmo com e sem o , sugerindo que ele é totalmente otimizado nesses casos.std::functionstd::bindstd::bindstd::functionstd::bind

O caso lambda é um pouco inútil no código otimizado. As diferenças de velocidade são tão pequenas que podem ter sido totalmente otimizadas pelo compilador.