Quanto uso de macros "provável" e "improvável" é excessivo?

12

As frequentemente conhecidas como likelye unlikelymacros ajudam o compilador a saber se uma entrada ifgeralmente será inserida ou ignorada. Usá-lo resulta em algumas melhorias (um pouco menores) no desempenho.

Comecei a usá-los recentemente e não sei com que frequência essas dicas devem ser usadas. Atualmente, eu o uso com a verificação de erros ifs, que geralmente são marcados como unlikely. Por exemplo:

mem = malloc(size);
if (unlikely(mem == NULL))
  goto exit_no_mem;

Parece bom, mas as checagens de erros ifocorrem com bastante frequência e, conseqüentemente, o uso das macros mencionadas.

Minha pergunta é: é demais ter likelye unlikelymacros em todas as verificações de erros if?

Enquanto estamos nisso, que outros lugares são usados ​​com frequência?


No meu uso atual, está em uma biblioteca que faz uma abstração do subsistema em tempo real, para que os programas se tornem portáteis entre RTAI, QNX e outros. Dito isto, a maioria das funções é pequena e chama diretamente uma ou duas outras funções. Muitas são até static inlinefunções.

Portanto, antes de tudo, não é um aplicativo que eu possa criar um perfil. Não faz sentido "identificar gargalos", pois é uma biblioteca, não um aplicativo independente.

Segundo, é mais ou menos como "eu sei que isso é improvável, é melhor dizer ao compilador". Eu não tento ativamente otimizar o if.

Shahbaz
fonte
7
cheira a micro otimização para mim ...
catraca Anormal
2
Para o código do aplicativo, eu os adicionaria apenas se a criação de perfil mostrar que esse código é usado em um caminho ativo.
CodesInChaos
@ James, que apenas diz likelye unlikelyexiste e o que eles fazem. Não encontrei nada que realmente sugerisse quando e onde é melhor usá-los.
Shahbaz
@Shahbaz "Se a condição é freqüentemente falsa, a execução não é linear. Existe um grande pedaço de código não usado no meio que não apenas polui o L1i devido à pré-busca, mas também pode causar problemas na previsão de ramificação. a previsão do ramo está errada, a expressão condicional pode ser muito ineficiente ". Assim, laços apertados onde você deseja certificar-se as instruções necessárias estão em cache de L1i
James

Respostas:

12

Você precisa tanto de desempenho que esteja disposto a poluir seu código com isso? É uma otimização menor.

  • O código é executado em um loop apertado?
  • Seu aplicativo tem problemas de desempenho?
  • Você definiu o perfil do seu aplicativo e determinou que esse loop específico custa muito tempo da CPU?

A menos que você possa responder yesa todas as perguntas acima, não se preocupe com coisas assim.

Editar: em resposta à edição. Mesmo quando não é possível criar um perfil, geralmente é possível estimar pontos de acesso. Uma função de alocação de memória chamada por todos é uma boa candidata, especialmente porque requer apenas um único uso da macro para funcionar em toda a biblioteca.

Cara Java
fonte
1
Só para deixar claro, eu não votei contra você. No entanto, sua resposta realmente não responde à minha pergunta. Você está tentando dizer que a (un)likelymacro raramente é usada e apenas em código extremamente crítico de desempenho? É "má prática" usá-lo com frequência ou apenas "desnecessário"?
Shahbaz
@ Shahbaz Torna o código menos legível e a otimização do desempenho pode variar de ganho trivial a perda trivial. O último quando a suposição sobre a probabilidade estava incorreta ou mudou para incorreta devido a uma alteração posterior em outras partes do código. Se nunca deve ser usado, a menos que seja necessário.
Peter Peter
3
@ Peter: Embora seja muito ruim a sintaxe não ser mais agradável, notações sobre o que é provável ou improvável podem fornecer informações úteis para os seres humanos que estão lendo o código. Por exemplo, alguém que viu if (likely(x==2 || x==3)) doOneThing(); else switch(x) { ... }, poderia julgar que o uso de um ifpara os valores 2 e 3 não era meramente uma conseqüência do programador não saber que C pode associar dois caserótulos a um único manipulador.
Supercat
Ninguém mencionou o que considero um ponto crítico. Não é apenas que o caminho "improvável" ocorra com menos frequência; é possível que, dependendo do caminho que está ocorrendo, você não se importe com a velocidade. Por exemplo, um periférico fica sem resposta, então você deve redefini-lo e dormir de qualquer maneira.
Benjamin Lindqvist
2

Se você estiver escrevendo para x86 / x64 (e não estiver usando CPUs de 20 anos), o ganho de desempenho ao usar __builtin_expect () será desprezível, se houver. A razão disso é que as modernas CPUs x86 / x64 (embora não tenham 100% de certeza sobre o Atom), têm previsão dinâmica de ramificação, portanto, essencialmente, a CPU "aprende" sobre a ramificação que é usada com mais frequência. Claro, essas informações podem ser armazenadas apenas para um número limitado de ramificações; no entanto, existem apenas dois casos possíveis. Se (a) for uma ramificação "freqüentemente usada", seu programa se beneficiará dessa previsão dinâmica de ramificação e se (b) for ramificação "rara", você não verá realmente nenhum desempenho realista devido a erros de previsão em ramificações tão raras (20 ciclos de CPU de erro de previsão de ramificação não são MUITO ruins se ocorrer uma vez na lua azul).

NB: isso NÃO implica que, em x86 / x64 moderna, a importância de erros de ramificação tenha diminuído: qualquer ramificação com 50-50 de chance de salto no salto ainda sofrerá uma penalidade (IIRC 10-20 ciclos de CPU), portanto, nos loops internos, ramificações podem ainda precisa ser evitado. É apenas a importância de __builtin_expect () no x86 / x64 que diminuiu (IIRC, cerca de 10 a 15 anos atrás) - principalmente por causa da previsão dinâmica de ramificação.

NB2: para outras plataformas além de x86 / x64, YMMV.

Lebre sem insetos
fonte
Bem, o compilador sabe em qual ramificação a CPU espera ser menos provável. E pode providenciar para que seja realmente improvável. Mas o compilador provavelmente já conhece esse padrão sem a unlikelyanotação-
Deduplicator
@ Reduplicador: com previsão dinâmica de ramificação, o compilador não sabe qual ramificação é mais provável, pois a CPU a calcula em tempo de execução com base nas execuções anteriores até esse ponto do código.
No-Bugs Hare