Question

Ce n'est qu'un problème sur les versions de GCC avant 4.4, cela a été corrigé dans GCC 4.5.

Est-il possible de dire au compilateur que la variable utilisée dans un commutateur convient dans les cas énoncés?En particulier si c'est une petite plage et il y a un saut de la table générée.

extern int a;
main()
{
        switch (a & 0x7) {   // 0x7  == 111  values are 0-7
        case 0: f0(); break;
        case 1: f1(); break;
        case 2: f2(); break;
        case 3: f3(); break;
        case 4: f4(); break;
        case 5: f5(); break;
        case 6: f6(); break;
        case 7: f7(); break;
        }
}

J'ai essayé utilise xor pour les bits de poids faible (comme dans l'exemple), l'utilisation des enums, à l'aide de gcc_unreachable() en vain.Le code généré vérifie toujours si la variable est à l'intérieur de la gamme, l'ajout d'un inutile branche conditionnelle et en s'éloignant de la table jump code de calcul.

Note:c'est au plus profond de la boucle d'un décodeur, des questions de rendement de manière significative.

Il semble que je ne suis pas la seulement un.

Il n'y a aucun moyen de dire à gcc que la branche par défaut n'est jamais prise, bien qu'il omet la branche par défaut si elle peut prouver que l' la valeur n'est jamais hors de portée fondée sur les conditionnelle des contrôles.

Alors, comment faites-vous pour aider gcc prouver la variable s'adapte et il n'y a pas de branche par défaut dans l'exemple ci-dessus?(Sans l'ajout d'une branche conditionnelle, bien sûr.)

Les mises à jour

  1. C'était sur OS X 10.6 Snow Leopard avec CCAG 4.2 (par défaut à partir de Xcode.) Il n'est pas arrivé avec GCC 4.4/4.3 sous linux (rapporté par Nathon et Jens Gustedt.)

  2. Les fonctions dans l'exemple sont là pour des raisons de lisibilité, pense que ceux qui sont inline ou tout simplement des déclarations.Faire un appel de fonction sur x86 est cher.

    Aussi l'exemple, comme mentionné dans la note, appartient à l'intérieur d'une boucle sur des données (big data.)

    Le code généré par gcc 4.2/OS X est:

    [...]
    andl    $7, %eax
    cmpl    $7, %eax
    ja  L11
    mov %eax, %eax
    leaq    L20(%rip), %rdx
    movslq  (%rdx,%rax,4),%rax
    addq    %rdx, %rax
    jmp *%rax
    .align 2,0x90
    L20:
    .long   L12-L20
    .long   L13-L20
    .long   L14-L20
    .long   L15-L20
    .long   L16-L20
    .long   L17-L20
    .long   L18-L20
    .long   L19-L20
    L19:
    [...]
    

    Le problème se situe sur cmp $7, %eax; ja L11;

  3. OK, je vais avec le laid solution et l'ajout d'un cas particulier pour les versions de gcc 4.4 ci-dessous à l'aide d'une autre version sans interrupteur et l'utilisation de goto et de gcc &&label extensions.

    static void *jtb[] = { &&c_1, &&c_2, &&c_3, &&c_4, &&c_5, &&c_6, &&c_7, &&c_8 };
    [...]
    goto *jtb[a & 0x7];
    [...]
    while(0) {
    c_1:
    // something
    break;
    c_2:
    // something
    break;
    [...]
    }
    

    Remarque le tableau des étiquettes est statique, il n'est donc pas calculée à chaque appel.

Était-ce utile?

La solution

J'ai essayé de compiler quelque chose de simple et comparable avec -O5 et -fno-inline (mon f0-f7 fonctions ont été banal), et il a généré ce:


 8048420:   55                      push   %ebp ;; function preamble
 8048421:   89 e5                   mov    %esp,%ebp ;; Yeah, yeah, it's a function.
 8048423:   83 ec 04                sub    $0x4,%esp ;; do stuff with the stack
 8048426:   8b 45 08                mov    0x8(%ebp),%eax ;; x86 sucks, we get it
 8048429:   83 e0 07                and    $0x7,%eax ;; Do the (a & 0x7)
 804842c:   ff 24 85 a0 85 04 08    jmp    *0x80485a0(,%eax,4) ;; Jump table!
 8048433:   90                      nop
 8048434:   8d 74 26 00             lea    0x0(%esi,%eiz,1),%esi
 8048438:   8d 45 08                lea    0x8(%ebp),%eax
 804843b:   89 04 24                mov    %eax,(%esp)
 804843e:   e8 bd ff ff ff          call   8048400 
 8048443:   8b 45 08                mov    0x8(%ebp),%eax
 8048446:   c9                      leave  

Avez-vous essayer de jouer avec les niveaux d'optimisation?

Autres conseils

Vous pourriez peut-être utiliser un tableau de pointeurs de fonction à la place d'un interrupteur ?

#include <stdio.h>

typedef void (*func)(void);

static void f0(void) { printf("%s\n", __FUNCTION__); }
static void f1(void) { printf("%s\n", __FUNCTION__); }
static void f2(void) { printf("%s\n", __FUNCTION__); }
static void f3(void) { printf("%s\n", __FUNCTION__); }
static void f4(void) { printf("%s\n", __FUNCTION__); }
static void f5(void) { printf("%s\n", __FUNCTION__); }
static void f6(void) { printf("%s\n", __FUNCTION__); }
static void f7(void) { printf("%s\n", __FUNCTION__); }

int main(void)
{
    const func f[8] = { f0, f1, f2, f3, f4, f5, f6, f7 };
    int i;

    for (i = 0; i < 8; ++i)
    {
        f[i]();
    }
    return 0;
}

Avez-vous essayé de déclarer la switch variable comme un champ de bits?

struct Container {
  uint16_t a:3;
  uint16_t unused:13;
};

struct Container cont;

cont.a = 5;  /* assign some value */
switch( cont.a ) {
...
}

Espérons que cela fonctionne!

Je n'ai pas essayé, mais je ne suis pas sûr que gcc_unreachable fait la même chose que __builtin_unreachable.Googler les deux, gcc_unreachable semble être conçu comme une comme une affirmation outil pour le développement de GCC lui-même, peut-être avec une branche de prédiction de l'indice inclus, tandis que __builtin_unreachable rend le programme instantanément non définie — qui sonne comme la suppression du bloc de base, qui est ce que vous voulez.

http://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html#index-g_t_005f_005fbuiltin_005funreachable-3075

Peut-être il suffit d'utiliser un default étiquette pour la première ou la dernière affaire?

Cette question est tout à fait intéressante du point de vue d'oubli d'une optimisation du compilateur qui semble être une évidence pour nous, et j'ai passé beaucoup de temps à essayer de trouver une solution simple et efficace, en grande partie des personnels de la curiosité.

Cela dit, je dois l'avouer Je suis très sceptique que cet enseignement sera jamais entraîner une différence de performances mesurables dans la pratique, en particulier sur un nouveau mac.Si vous avez une quantité importante de données, vous serez dépendant des e/S, et une seule instruction ne sera jamais le goulot d'étranglement.Si vous avez une petite quantité de données, alors vous aurez besoin pour effectuer une beaucoup beaucoup beaucoup des calculs à plusieurs reprises avant d'une seule instruction va devenir un goulot d'étranglement.

Vous le poster du code de montrer qu'il y a vraiment une différence de performance?Ou de décrire le code et les données de votre travail avec?

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top