Me deparei com um #define
em que eles usam __builtin_expect
.
A documentação diz:
Função interna:
long __builtin_expect (long exp, long c)
Você pode usar
__builtin_expect
para fornecer ao compilador informações de previsão de ramificação. Em geral, você deve preferir usar o feedback real do perfil para isso (-fprofile-arcs
), pois os programadores são notoriamente ruins em prever o desempenho de seus programas. No entanto, existem aplicativos em que esses dados são difíceis de coletar.O valor de retorno é o valor de
exp
, que deve ser uma expressão integral. A semântica do built-in é que é esperado issoexp == c
. Por exemplo:if (__builtin_expect (x, 0)) foo ();
indicaria que não esperamos ligar
foo
, pois esperamosx
ser zero.
Então, por que não usar diretamente:
if (x)
foo ();
em vez da sintaxe complicada com __builtin_expect
?
if ( x == 0) {} else foo();
.. ou simplesmente oif ( x != 0 ) foo();
que é equivalente ao código da documentação do GCC.Respostas:
Imagine o código do assembly que seria gerado a partir de:
Eu acho que deveria ser algo como:
Você pode ver que as instruções estão dispostas em uma ordem que o
bar
caso precede ofoo
caso (em oposição ao código C). Isso pode utilizar melhor o pipeline da CPU, pois um salto frustra as instruções já obtidas.Antes do salto ser executado, as instruções abaixo (o
bar
caso) são enviadas ao pipeline. Como ofoo
caso é improvável, saltar também é improvável; portanto, é improvável que tropeçar no pipeline.fonte
x = 0
que a barra seja dada primeiro. E foo, é definido mais tarde, pois suas chances (em vez de probabilidade de uso) são menores, certo?Vamos descompilar para ver o que o GCC 4.8 faz com ele
Blagovest mencionou a inversão de ramificação para melhorar o pipeline, mas os compiladores atuais realmente o fazem? Vamos descobrir!
Sem
__builtin_expect
Compile e descompile com o GCC 4.8.2 x86_64 Linux:
Resultado:
A ordem das instruções na memória permaneceu inalterada: primeiro o
puts
e depoisretq
retornamos.Com
__builtin_expect
Agora substitua
if (i)
por:e temos:
O
puts
foi movido para o final da função, oretq
retorno!O novo código é basicamente o mesmo que:
Essa otimização não foi concluída
-O0
.Mas boa sorte em escrever um exemplo que seja mais rápido do
__builtin_expect
que sem, as CPUs são realmente inteligentes naqueles dias . Minhas tentativas ingênuas estão aqui .C ++ 20
[[likely]]
e[[unlikely]]
O C ++ 20 padronizou os recursos internos do C ++: Como usar o atributo provável / improvável do C ++ 20 na instrução if-else Eles provavelmente (um trocadilho!) Farão a mesma coisa.
fonte
A idéia de
__builtin_expect
é informar ao compilador que você normalmente descobrirá que a expressão é avaliada como c, para que o compilador possa otimizar para esse caso.Eu acho que alguém pensou que eles estavam sendo espertos e que eles estavam acelerando as coisas fazendo isso.
Infelizmente, a menos que a situação seja muito bem compreendida (é provável que eles não tenham feito isso), pode ter piorado as coisas. A documentação ainda diz:
Em geral, você não deve usar a
__builtin_expect
menos que:fonte
__builtin_expect
ou não . Por outro lado, o compilador pode executar muitas otimizações com base na probabilidade da ramificação, como organizar o código para que o atalho seja contíguo, movendo o código com pouca probabilidade de ser otimizado ainda mais ou reduzindo seu tamanho, tomando decisões sobre quais ramificações vetorizar, agendar melhor o caminho mais ativo e assim por diante.Bem, como diz a descrição, a primeira versão adiciona um elemento preditivo à construção, dizendo ao compilador que o
x == 0
ramo é o mais provável - ou seja, é o ramo que será utilizado com mais frequência pelo seu programa.Com isso em mente, o compilador pode otimizar o condicional para que exija a menor quantidade de trabalho quando a condição esperada for mantida, às custas de talvez ter que fazer mais trabalho em caso de condição inesperada.
Veja como os condicionais são implementados durante a fase de compilação e também no assembly resultante, para ver como um ramo pode ser menos trabalhoso que o outro.
No entanto, eu esperaria que essa otimização tivesse um efeito perceptível se o condicional em questão fizer parte de um loop interno apertado que é chamado muito , pois a diferença no código resultante é relativamente pequena. E se você otimizá-lo da maneira errada, poderá diminuir seu desempenho.
fonte
compiler design - Aho, Ullmann, Sethi
:-)Não vejo nenhuma das respostas abordando a pergunta que acho que você estava fazendo, parafraseada:
O título da sua pergunta me fez pensar dessa maneira:
Se o compilador assumir que 'true' é mais provável, ele poderá otimizar para não chamar
foo()
.O problema aqui é que, em geral, você não sabe o que o compilador assumirá - portanto, qualquer código que use esse tipo de técnica precisará ser cuidadosamente medido (e possivelmente monitorado ao longo do tempo, se o contexto mudar).
fonte
else
foi deixado de fora do corpo do post.Eu testo no Mac de acordo com @Blagovest Buyukliev e @Ciro. As assembléias parecem claras e eu adiciono comentários;
Os comandos são
gcc -c -O3 -std=gnu11 testOpt.c; otool -tVI testOpt.o
Quando eu uso -O3, parece o mesmo, independentemente de o __builtin_expect (i, 0) existir ou não.
Ao compilar com -O2, parece diferente com e sem __builtin_expect (i, 0)
Primeiro sem
Agora com __builtin_expect (i, 0)
Para resumir, __builtin_expect funciona no último caso.
fonte