Puis-je suggérer l'optimiseur en donnant la portée d'un entier?

j'utilise un type int pour stocker une valeur. Par la sémantique du programme, la valeur varie toujours dans une très petite plage (0 - 36), et int (pas un char ) est utilisé uniquement en raison de L'efficacité du CPU.

il semble que de nombreuses optimisations arithmétiques spéciales peuvent être effectuées sur une si petite gamme d'entiers. Beaucoup d'appels de fonction sur ces entiers pourraient être optimisés dans un petit ensemble d'opérations "magiques", et certaines fonctions peuvent même être optimisé dans la recherche de table-ups.

Alors, est-il possible de dire au compilateur que cette int est toujours dans ce petit intervalle, et est-il possible pour le compilateur de faire ces optimisations?

169
demandé sur Peter Mortensen 2016-11-06 11:00:12
la source

4 ответов

Oui, c'est possible. Par exemple, pour gcc vous pouvez utiliser __builtin_unreachable pour informer le compilateur des conditions impossibles, comme ceci:

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

nous pouvons envelopper la condition ci-dessus dans une macro:

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

Et de l'utiliser de la sorte:

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

comme vous pouvez voir , gcc effectue des optimisations basées sur cette information:

#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;
    }
}

produit:

func(int):
    mov     eax, 17
    ret

un inconvénient, cependant, que si votre code casse jamais de telles hypothèses, vous obtenez un comportement non défini .

il ne vous avertit pas quand cela se produit, même dans les constructions de débogage. Pour déboguer/tester/attraper des bogues avec des suppositions plus facilement, vous pouvez utiliser une macro hybride assumez/assert (crédits à @David Z), comme celle-ci:

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

dans debug builds (avec NDEBUG Non défini), il fonctionne comme un ordinaire assert , message d'erreur d'impression et abort programme ing, et dans les constructions de libération, il fait usage d'une hypothèse, la production de code optimisé.

notez, cependant, qu'il ne remplace pas le normal assert - cond reste dans les constructions de version, donc vous ne devriez pas faire quelque chose comme assume(VeryExpensiveComputation()) .

222
répondu deniss 2016-11-21 19:35:19
la source

il y a un support standard pour cela. Ce que vous devez faire est d'inclure stdint.h ( cstdint ) et ensuite utiliser le type uint_fast8_t .

indique au compilateur que vous n'utilisez que des nombres entre 0 et 255, mais qu'il est libre d'utiliser un type plus grand si cela donne du code plus rapide. De même, le compilateur peut supposer que la variable n'aura jamais une valeur supérieure à 255 et faire ensuite des optimisations en conséquence.

60
répondu Lundin 2016-11-07 15:46:34
la source

la réponse actuelle est bonne pour le cas où vous savez pour sûr Quelle est la gamme, mais si vous voulez toujours un comportement correct quand la valeur est en dehors de la gamme attendue, alors il ne fonctionnera pas.

pour ce cas, j'ai trouvé que cette technique peut fonctionner:

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

L'idée est un compromis code-données: vous déplacez 1 bit de données (si x == c ) dans logique de contrôle .

Cela indique à l'optimiseur que x est en fait une constante connue c , l'encourageant à s'aligner et optimiser la première invocation de foo séparément du reste, peut-être assez lourdement.

assurez-vous de réellement prendre en compte le code dans un sous-programme unique foo , cependant -- ne dupliquez pas le code.

exemple:

pour que cette technique fonctionne vous devez avoir un peu de chance -- il y a des cas où le compilateur décide de ne pas évaluer les choses statiquement, et ils sont un peu arbitraires. Mais quand ça marche, ça marche bien:

#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);
}

il suffit d'utiliser -O3 et de noter les constantes pré-évaluées 0x20 et 0x30e dans la sortie de l'assembleur .

9
répondu Mehrdad 2016-11-08 23:10:59
la source

je lance juste pour dire que si vous voulez une solution plus standard C++, vous pouvez utiliser l'attribut [[noreturn]] pour écrire votre propre unreachable .

donc je vais ré-utiliser deniss' excellent exemple pour démontrer:

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;
    }
}

qui comme vous pouvez voir , donne un code presque identique:

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

le revers de La médaille est bien sûr, que vous obtenez un avertissement qu'une fonction [[noreturn]] retourne effectivement.

5
répondu StoryTeller 2017-05-23 14:47:01
la source