Estou usando um int
tipo para armazenar um valor. Pela semântica do programa, o valor sempre varia em um intervalo muito pequeno (0 - 36) e int
(não a char
) é usado apenas devido à eficiência da CPU.
Parece que muitas otimizações aritméticas especiais podem ser executadas em um intervalo tão pequeno de números inteiros. Muitas chamadas de função nesses números inteiros podem ser otimizadas em um pequeno conjunto de operações "mágicas", e algumas funções podem até ser otimizadas em consultas de tabela.
Portanto, é possível dizer ao compilador que isso int
está sempre nesse pequeno intervalo e é possível que o compilador faça essas otimizações?
unsigned
tipos, pois são mais fáceis de raciocinar com o compilador.var value: 0..36;
.int
e tambémunsigned int
precisa ser estendido por zero ou zero de 32 para 64 bits, na maioria dos sistemas com ponteiros de 64 bits. Observe que no x86-64, as operações nos registradores de 32 bits estendem de zero a 64 bits gratuitamente (sem estender sinal, mas o estouro assinado é um comportamento indefinido, portanto, o compilador pode usar apenas matemática assinada de 64 bits, se desejar). Portanto, você vê apenas instruções extras para estender zero argumentos de função de 32 bits, não resultados da computação. Você faria para tipos não assinados mais estreitos.Respostas:
Sim, é possível. Por exemplo,
gcc
você pode usar__builtin_unreachable
para informar o compilador sobre condições impossíveis, como:Podemos quebrar a condição acima em uma macro:
E use-o assim:
Como você pode ver ,
gcc
realiza otimizações com base nessas informações:Produz:
Uma desvantagem, no entanto, é que, se seu código quebrar essas suposições, você terá um comportamento indefinido .
Ele não o notifica quando isso acontece, mesmo nas versões de depuração. Para depurar / testar / capturar bugs com suposições mais facilmente, você pode usar uma macro híbrida de assumir / afirmar (créditos para @David Z), como esta:
Nas compilações de depuração (com
NDEBUG
não definidas), ele funciona como umaassert
mensagem de erro comum eabort
programa de impressão, e nas compilações de versão utiliza uma suposição, produzindo código otimizado.Observe, no entanto, que ele não substitui os permanentes regulares
assert
-cond
nas versões de lançamento, portanto você não deve fazer algo assimassume(VeryExpensiveComputation())
.fonte
return 2
ramo foi eliminado do código pelo compilador.__builtin_expect
é uma dica não estrita.__builtin_expect(e, c)
deve ler como "e
provavelmente avaliarác
" e pode ser útil para otimizar a previsão de ramificação, mas não se restringee
a semprec
, portanto, não permite que o otimizador jogue fora outros casos. Veja como as ramificações são organizadas em montagem .__builtin_unreachable()
.assert
, por exemplo, definirassume
comoassert
quandoNDEBUG
não está definido e como__builtin_unreachable()
quandoNDEBUG
está definido. Dessa forma, você obtém o benefício da suposição no código de produção, mas em uma compilação de depuração, você ainda tem uma verificação explícita. É claro que você precisa fazer testes suficientes para garantir que a suposição será satisfeita na natureza.Existe suporte padrão para isso. O que você deve fazer é incluir
stdint.h
(cstdint
) e, em seguida, usar o tipouint_fast8_t
.Isso informa ao compilador que você está usando apenas números entre 0 e 255, mas que é livre usar um tipo maior se isso fornecer um código mais rápido. Da mesma forma, o compilador pode assumir que a variável nunca terá um valor acima de 255 e, em seguida, fará otimizações de acordo.
fonte
uint_fast8_t
é realmente um tipo de 8 bits (por exemplounsigned char
) como em x86 / ARM / MIPS / PPC ( godbolt.org/g/KNyc31 ). No início do DEC Alpha antes de 21164A , as cargas / lojas de bytes não eram suportadas, portanto qualquer implementação sã seria usadatypedef uint32_t uint_fast8_t
. AFAIK, não há mecanismo para um tipo ter limites de alcance extras com a maioria dos compiladores (como o gcc), por isso tenho certeza deuint_fast8_t
que eles se comportariam exatamente da mesma formaunsigned int
ou em qualquer outro caso.bool
é especial e tem intervalo limitado a 0 ou 1, mas é um tipo interno, não definido pelos arquivos de cabeçalho em termos dechar
, no gcc / clang. Como eu disse, não acho que a maioria dos compiladores tenha um mecanismo que tornaria isso possível).uint_fast8_t
é uma boa recomendação, pois ele usará um tipo de 8 bits em plataformas onde é tão eficiente quantounsigned int
. (Eu estou realmente não sei o que osfast
tipos devem ser rápido para , e se o cache pegada tradeoff é suposto ser parte dela.). O x86 possui amplo suporte para operações de bytes, mesmo para adicionar bytes com uma fonte de memória, para que você nem precise fazer uma carga separada de extensão zero (o que também é muito barato). O gcc criauint_fast16_t
um tipo de 64 bits no x86, o que é insano para a maioria dos usos (vs. 32 bits). godbolt.org/g/Rmq5bv .A resposta atual é boa para o caso quando você sabe ao certo qual é o intervalo, mas se ainda deseja um comportamento correto quando o valor está fora do intervalo esperado, ele não funcionará.
Nesse caso, achei que essa técnica pode funcionar:
A idéia é uma troca de dados de código: você está movendo 1 bit de dados (se
x == c
) para a lógica de controle .Isso sugere que o otimizador
x
é de fato uma constante conhecidac
, incentivando-o a alinhar e otimizar a primeira chamada defoo
separadamente do resto, possivelmente muito.foo
Entretanto, certifique-se de fatorar o código em uma única sub-rotina - não duplique o código.Exemplo:
Para que essa técnica funcione, você precisa ter um pouco de sorte - há casos em que o compilador decide não avaliar as coisas estaticamente, e elas são arbitrárias. Mas quando funciona, funciona bem:
Basta usar
-O3
e observar as constantes pré-avaliadas0x20
e0x30e
na saída do assembler .fonte
if (x==c) foo(c) else foo(x)
? Se apenas para pegarconstexpr
implementações defoo
?constexpr
era uma coisa e nunca me preocupei em atualizá-la depois (embora eu nunca tenha me preocupado em me preocupar com issoconstexpr
depois), mas a razão pela qual eu não fiz isso inicialmente foi porque eu queria facilite para o compilador classificá-los como código comum e remova a ramificação se ele decidir deixá-los como chamadas de método normais e não otimizar. Eu esperava que, se eu colocassec
, é realmente difícil para o compilador c (desculpe, piada de mau gosto) que os dois sejam do mesmo código, embora eu nunca tenha verificado isso.Estou apenas dizendo que, se você quiser uma solução mais C ++ padrão, poderá usar o
[[noreturn]]
atributo para escrever sua própriaunreachable
.Então, refiz o excelente exemplo de deniss para demonstrar:
O que, como você pode ver , resulta em código quase idêntico:
A desvantagem é, obviamente, que você recebe um aviso de que uma
[[noreturn]]
função realmente retorna.fonte
clang
, quando minha solução original não funciona , truque tão bom e +1. Mas tudo depende muito do compilador (como Peter Cordes nos mostrou,icc
pois pode piorar o desempenho), portanto ainda não é universalmente aplicável. Além disso, observação secundária: aunreachable
definição deve estar disponível para o otimizador e incorporada para que isso funcione .