17 Stimmen

GCC 4.4: Bereichsprüfung bei switch/case-Anweisung in GCC vermeiden?

Dies ist nur ein Problem auf GCC-Versionen vor 4.4, dies wurde in GCC 4.5 behoben.

Ist es möglich, dem Compiler mitzuteilen, dass die in einem Switch verwendete Variable in die vorgesehenen Case-Anweisungen passt? Insbesondere, wenn es sich um einen kleinen Bereich handelt und eine Sprungtabelle erzeugt wird.

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

Ich habe versucht, xor'ing zu niedrigen Bits (wie das Beispiel), mit enums, mit gcc_unreachable() ohne Erfolg. Der generierte Code prüft immer, ob die Variable innerhalb des Bereichs liegt, fügt eine sinnlose Verzweigungsbedingung hinzu und verschiebt den Sprungtabellenberechnungscode weg.

Hinweis: Diese Funktion befindet sich in der innersten Schleife eines Decoders, was die Leistung erheblich beeinflusst.

Ich bin wohl nicht der sólo eine .

Es gibt keine Möglichkeit, dem gcc mitzuteilen, dass der Standardzweig nie genommen wird, obwohl er den Standardzweig auslässt, wenn er beweisen kann, dass die Wert niemals außerhalb des Bereichs liegt, basierend auf früheren bedingten Prüfungen.

Also, wie helfen Sie gcc beweisen, dass die Variable passt und es keine Standardverzweigung im obigen Beispiel gibt? (Ohne eine bedingte Verzweigung hinzuzufügen, natürlich.)

Aktualisierungen

  1. Dies war auf OS X 10.6 Snow Leopard mit GCC 4.2 (Standard von Xcode.) Es geschah nicht mit GCC 4.4/4.3 in Linux (berichtet von Nathon und Jens Gustedt.)

  2. Die Funktionen in dem Beispiel dienen der Lesbarkeit, denken Sie daran, dass sie inlined oder nur Anweisungen sind. Ein Funktionsaufruf auf x86 ist teuer.

    Auch das Beispiel gehört, wie in der Anmerkung erwähnt, in eine Schleife über Daten (Big Data).

    Der generierte Code mit gcc 4.2/OS X ist:

    [...]
    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:
    [...]

    Das Problem liegt in cmp $7, %eax; ja L11;

  3. OK, ich gehe mit der hässlichen Lösung und fügen Sie einen speziellen Fall für gcc Versionen unter 4.4 mit einer anderen Version ohne einen Schalter und mit goto und gcc &&label Erweiterungen.

    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;
    [...]
    }

    Beachten Sie, dass das Array der Labels statisch ist und nicht bei jedem Aufruf neu berechnet wird.

6voto

Paul R Punkte 201623

Vielleicht könnten Sie statt eines Schalters ein Array von Funktionszeigern verwenden?

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

2voto

Praetorian Punkte 103296

Haben Sie versucht, die switch Variable als Bitfeld?

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

struct Container cont;

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

Hoffentlich funktioniert das!

2voto

Potatoswatter Punkte 130562

Ich habe es nicht versucht, aber ich bin nicht sicher, dass gcc_unreachable tut das Gleiche wie __builtin_unreachable . Ich habe die beiden gegoogelt, gcc_unreachable scheint als Assertion-Tool für die Entwicklung des GCC selbst gedacht zu sein, vielleicht mit einem Hinweis auf eine Verzweigungsvorhersage, während __builtin_unreachable macht das Programm sofort undefiniert - was so klingt, als würde man den Basisblock löschen, was man ja auch will.

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

1voto

Jens Gustedt Punkte 74457

Vielleicht verwenden Sie einfach eine default Etikett für den ersten oder letzten Fall?

1voto

nmichaels Punkte 47432

Ich habe versucht, etwas Einfaches und Vergleichbares mit -O5 und -fno-inline zu kompilieren (meine f0-f7-Funktionen waren trivial), und es erzeugte dies:

 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  

Haben Sie versucht, mit Optimierungsstufen zu spielen?

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X