c++ - tipos - tipo de dado void




Posso sugerir o otimizador, dando o intervalo de um inteiro? (3)

Eu estou usando um tipo int para armazenar um valor. Pela semântica do programa, o valor sempre varia em um intervalo muito pequeno (0 - 36), e int (não um char ) é usado somente por causa da eficiência da CPU.

Parece que muitas otimizações aritméticas especiais podem ser realizadas em um intervalo tão pequeno de inteiros. Muitas chamadas de função nos números inteiros podem ser otimizadas em um pequeno conjunto de operações "mágicas", e algumas funções podem até ser otimizadas em pesquisas de tabela.

Então, é possível dizer ao compilador que este int está sempre nesse pequeno intervalo, e é possível que o compilador faça essas otimizações?


A resposta atual é boa para o caso em que você sabe com certeza qual é o intervalo, mas se ainda quiser o comportamento correto quando o valor estiver fora do intervalo esperado, ele não funcionará.

Para esse caso, descobri que essa técnica pode funcionar:

if (x == c)  // assume c is a constant
{
    foo(x);
}
else
{
    foo(x);
}

A ideia é 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 ao otimizador que x é de fato uma constante conhecida c , encorajando-o a inline e otimizar a primeira invocação de foo separadamente dos demais, possivelmente com bastante intensidade.

Certifique-se de fatorar o código em uma única sub-rotina foo - não duplique o código.

Exemplo:

Para que esta 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:

#include <math.h>
#include <stdio.h>

unsigned foo(unsigned x)
{
    return x * (x + 1);
}

unsigned bar(unsigned x) { return foo(x + 1) + foo(2 * x); }

int main()
{
    unsigned x;
    scanf("%u", &x);
    unsigned r;
    if (x == 1)
    {
        r = bar(bar(x));
    }
    else if (x == 0)
    {
        r = bar(bar(x));
    }
    else
    {
        r = bar(x + 1);
    }
    printf("%#x\n", r);
}

Apenas use -O3 e observe as constantes pré-avaliadas 0x20 e 0x30e na saída do montador .


Eu estou apenas falando para dizer que se você quiser uma solução que seja mais padrão C ++, você pode usar o atributo [[noreturn]] para escrever seu próprio unreachable .

Então eu vou re-purpose excelente exemplo de deniss para demonstrar:

namespace detail {
    [[noreturn]] void unreachable(){}
}

#define assume(cond) do { if (!(cond)) detail::unreachable(); } while (0)

int func(int x){
    assume(x >=0 && x <= 10);

    if (x > 11){
        return 2;
    }
    else{
        return 17;
    }
}

Que, como você pode ver , resulta em código quase idêntico:

detail::unreachable():
        rep ret
func(int):
        movl    $17, %eax
        ret

A desvantagem é que você recebe um aviso de que uma função [[noreturn]] , de fato, retorna.


Sim, é possível. Por exemplo, para o gcc você pode usar __builtin_unreachable para informar ao compilador sobre condições impossíveis, como:

if (value < 0 || value > 36) __builtin_unreachable();

Podemos envolver a condição acima em uma macro:

#define assume(cond) do { if (!(cond)) __builtin_unreachable(); } while (0)

E use assim:

assume(x >= 0 && x <= 10);

Como você pode ver , o gcc executa otimizações com base nessas informações:

#define assume(cond) do { if (!(cond)) __builtin_unreachable(); } while (0)

int func(int x){
    assume(x >=0 && x <= 10);

    if (x > 11){
        return 2;
    }
    else{
        return 17;
    }
}

Produz:

func(int):
    mov     eax, 17
    ret

Uma desvantagem, no entanto, é que, se seu código quebrar essas suposições, você terá um comportamento indefinido .

Ele não avisa quando isso acontece, mesmo em compilações de depuração. Para depurar / testar / capturar bugs com suposições mais facilmente, você pode usar uma macro de assumir / afirmar híbrida (créditos para @David Z), como este:

#if defined(NDEBUG)
#define assume(cond) do { if (!(cond)) __builtin_unreachable(); } while (0)
#else
#include <cassert>
#define assume(cond) assert(cond)
#endif

Nas compilações de depuração (com o NDEBUG não definido), ele funciona como uma assert comum, imprimindo mensagens de erro e abort o programa, e em compilações de lançamentos ele faz uso de uma suposição, produzindo código otimizado.

Note, no entanto, que não é um substituto para assert - cond regular permanece em compilações de lançamento, então você não deve fazer algo como assume(VeryExpensiveComputation()) .





compiler-optimization