Para as arquiteturas Intel, há uma maneira de instruir o compilador GCC a gerar código que sempre force a previsão de ramificação de uma maneira particular em meu código? O hardware Intel ainda suporta isso? E quanto a outros compiladores ou hardwares?
Eu usaria isso em código C ++, onde conheço o caso em que desejo executar mais rápido e não me importo com a lentidão quando o outro branch precisa ser executado, mesmo quando ele recentemente o fez.
for (;;) {
if (normal) { // How to tell compiler to always branch predict true value?
doSomethingNormal();
} else {
exceptionalCase();
}
}
Como uma questão de acompanhamento para Evdzhan Mustafa, a dica pode apenas especificar uma dica para a primeira vez que o processador encontra a instrução, todas as previsões de ramificações subsequentes funcionando normalmente?
Respostas:
A partir do C ++ 20, os atributos prováveis e improváveis devem ser padronizados e já são suportados no g ++ 9 . Como discutido aqui , você pode escrever
por exemplo, no código a seguir o bloco else fica embutido graças ao
[[unlikely]]
no bloco iflink godbolt comparando a presença / ausência do atributo
fonte
[[unlikely]]
emif
vs[[likely]]
noelse
?O GCC suporta a função
__builtin_expect(long exp, long c)
de fornecer esse tipo de recurso. Você pode verificar a documentação aqui .Onde
exp
está a condição usada ec
é o valor esperado. Por exemplo, no caso de você quererPor causa da sintaxe estranha, isso geralmente é usado definindo duas macros personalizadas como
apenas para facilitar a tarefa.
Pense nisso:
fonte
constexpr
função?constexpr
função possa substituir essa macro. Tem que estarif
diretamente na declaração, eu acredito. A mesma razãoassert
nunca poderia ser umaconstexpr
função.constexpr
fala apenas sobre semântica de valor, não o inlining de assembly específico de implementação); a interpretação direta (sem inline) do código não tem sentido. Não há razão para usar uma função para isso.__builtin_expect
si é uma dica de otimização, então argumentar que um método que simplifica seu uso depende de otimização não é ... convincente. Além disso, não adicionei oconstexpr
especificador para fazê-lo funcionar em primeiro lugar, mas para fazê-lo funcionar em expressões constantes. E sim, existem razões para usar uma função. Por exemplo, eu não gostaria de poluir todo o meu namespace com um nome bonitinho comolikely
. Eu teria que usarLIKELY
, por exemplo , para enfatizar que é uma macro e evitar colisões, mas isso é simplesmente feio.gcc has long __builtin_expect (long exp, long c) ( ênfase minha ):
Conforme a documentação observa, você deve preferir usar feedback de perfil real e este artigo mostra um exemplo prático disso e como isso no caso deles, pelo menos, acaba sendo uma melhoria em relação ao uso
__builtin_expect
. Veja também Como usar otimizações guiadas por perfil no g ++? .Também podemos encontrar um artigo para iniciantes no kernel do Linux sobre as macros kernal provável () e improvável () que usam este recurso:
Observe o
!!
usado na macro, podemos encontrar a explicação para isso em Por que usar !! (condição) em vez de (condição)? .Só porque essa técnica é usada no kernel do Linux não significa que sempre faz sentido usá-la. Podemos ver a partir desta pergunta que respondi recentemente a diferença entre o desempenho da função ao passar parâmetro como constante de tempo de compilação ou variável que muitas técnicas de otimizações roladas à mão não funcionam no caso geral. Precisamos criar o perfil do código com cuidado para entender se uma técnica é eficaz. Muitas técnicas antigas podem nem mesmo ser relevantes para as otimizações de compiladores modernos.
Observe, embora os builtins não sejam portáteis, o clang também oferece suporte a __builtin_expect .
Além disso, em algumas arquiteturas, pode não fazer diferença .
fonte
Não, não há. (Pelo menos em processadores x86 modernos.)
__builtin_expect
mencionado em outras respostas influencia a maneira como o gcc organiza o código assembly. Ele não influencia diretamente o preditor de ramificação da CPU. Claro, haverá efeitos indiretos na previsão de ramificação causados pela reordenação do código. Mas nos processadores x86 modernos não há nenhuma instrução que diga à CPU "assuma que este ramo foi / não foi usado".Consulte esta pergunta para obter mais detalhes: Intel x86 0x2E / 0x3E Prefix Branch Prediction realmente usado?
Para ser claro,
__builtin_expect
e / ou o uso de-fprofile-arcs
pode melhorar o desempenho do seu código, dando dicas ao preditor de branch por meio do layout do código (consulte Otimizações de desempenho do assembly x86-64 - Alinhamento e previsão de branch ) e também melhorando o comportamento do cache mantendo o código "improvável" longe do código "provável".fonte
__builtin_expect
.__builtin_expect
. Portanto, este deve ser apenas um comentário. Mas não é falso, então removi meu voto negativo.__builtin_expect
para criar trivialmente um caso de teste com o qual possa medir eperf stat
que terá uma taxa muito alta de erro de estimativa de branch. Afeta apenas o layout da filial . E, a propósito, a Intel desde Sandybridge ou pelo menos Haswell não usa previsão estática muito / nada; há sempre alguma previsão no BHT, seja um pseudônimo obsoleto ou não. xania.org/201602/bpu-part-twoA maneira correta de definir macros prováveis / improváveis em C ++ 11 é a seguinte:
Este método é compatível com todas as versões C ++, ao contrário
[[likely]]
, mas depende de uma extensão não padrão__builtin_expect
.Quando essas macros são definidas desta forma:
Isso pode mudar o significado das
if
instruções e quebrar o código. Considere o seguinte código:E sua saída:
Como você pode ver, a definição de PROVÁVEL usar
!!
como um elenco parabool
quebrar a semântica deif
.O ponto aqui não é isso
operator int()
eoperator bool()
deve estar relacionado. O que é uma boa prática.Em vez disso, usar em
!!(x)
vez destatic_cast<bool>(x)
perde o contexto para as conversões contextuais do C ++ 11 .fonte
switch
, obrigado. A conversão contextual envolvida aqui é partucluar para o tipobool
e os cinco contextos específicos listados lá , que não incluem oswitch
contexto.(_Bool)(condition)
, porque C não tem sobrecarga de operador.(condition)
, não!!(condition)
. Ambos estãotrue
depois de mudar isso (testado com g ++ 7.1). Você pode construir um exemplo que realmente demonstre o problema do qual está falando quando usa!!
para booleanizar?Como todas as outras respostas sugeriram adequadamente, você pode usar
__builtin_expect
para dar ao compilador uma dica sobre como organizar o código do assembly. Como os documentos oficiais apontam, na maioria dos casos, o montador embutido em seu cérebro não será tão bom quanto o criado pela equipe do GCC. É sempre melhor usar dados de perfil reais para otimizar seu código, em vez de adivinhar.Ao longo de linhas semelhantes, mas ainda não mencionadas, é uma maneira específica do GCC para forçar o compilador a gerar código em um caminho "frio". Isso envolve o uso dos atributos
noinline
ecold
, que fazem exatamente o que parecem que fazem. Esses atributos só podem ser aplicados a funções, mas com C ++ 11, você pode declarar funções lambda embutidas e esses dois atributos também podem ser aplicados a funções lambda.Embora isso ainda se enquadre na categoria geral de micro-otimização e, portanto, o conselho padrão se aplica - teste, não adivinhe -, sinto que é mais útil do que geralmente
__builtin_expect
. Quase nenhuma geração do processador x86 usa dicas de previsão de ramificação ( referência ), então a única coisa que você poderá afetar de qualquer maneira é a ordem do código de montagem. Como você sabe o que é código de manipulação de erros ou "caso extremo", pode usar esta anotação para garantir que o compilador nunca irá prever um branch para ele e irá vinculá-lo fora do código "quente" ao otimizar o tamanho.Uso de amostra:
Melhor ainda, o GCC irá ignorar isso automaticamente em favor do feedback do perfil quando estiver disponível (por exemplo, ao compilar com
-fprofile-use
).Veja a documentação oficial aqui: https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#Common-Function-Attributes
fonte
__builtin_expect
faz. Não é inútil. Você está certo ao dizer que ocold
atributo também é útil, mas você subestima a utilidade de,__builtin_expect
eu acho.__builtin_expect pode ser usado para dizer ao compilador para que lado você espera que um branch vá. Isso pode influenciar como o código é gerado. Os processadores típicos executam o código sequencialmente com mais rapidez. Então, se você escrever
o compilador irá gerar código como
Se sua dica estiver correta, isso executará o código sem quaisquer ramificações realmente executadas. Ele será executado mais rápido do que a sequência normal, onde cada instrução if ramificará em torno do código condicional e executará três ramificações.
Os processadores x86 mais novos têm instruções para ramificações que se espera que sejam obtidas ou para ramificações que se espera que não sejam usadas (há um prefixo de instrução; não tenho certeza sobre os detalhes). Não tenho certeza se o processador usa isso. Não é muito útil, porque a previsão de ramificação resolverá isso muito bem. Portanto, não acho que você possa realmente influenciar a previsão do branch .
fonte
Com relação ao OP, não, não há como no GCC dizer ao processador para sempre assumir que o branch foi ou não tomado. O que você tem é __builtin_expect, que faz o que os outros dizem que faz. Além disso, acho que você não quer dizer ao processador se o ramo é levado ou não sempre . Os processadores de hoje, como a arquitetura Intel, podem reconhecer padrões bastante complexos e se adaptar com eficácia.
No entanto, há momentos em que você deseja assumir o controle se, por padrão, uma ramificação é prevista ou não: Quando você souber, o código será chamado de "frio" com relação às estatísticas de ramificação.
Um exemplo concreto: código de gerenciamento de exceção. Por definição, o código de gerenciamento acontecerá excepcionalmente, mas talvez quando ocorrer o desempenho máximo seja desejado (pode haver um erro crítico para ser eliminado o mais rápido possível), portanto, você pode querer controlar a previsão padrão.
Outro exemplo: você pode classificar sua entrada e pular para o código que trata o resultado de sua classificação. Se houver muitas classificações, o processador pode coletar estatísticas, mas perdê-las porque a mesma classificação não ocorre em tempo suficiente e os recursos de previsão são dedicados ao código recentemente chamado. Gostaria que houvesse um primitivo para dizer ao processador "por favor, não dedique recursos de previsão a este código" da maneira que às vezes você pode dizer "não coloque isso em cache".
fonte