Frage

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

Ist es möglich, dem Compiler mitzuteilen, dass die in einem Schalter verwendete Variable in die bereitgestellten Case-Anweisungen passt?Insbesondere, wenn es sich um einen kleinen Bereich handelt und eine Sprungtabelle generiert 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, auf niedrige Bits zu xorieren (wie im Beispiel), Enumerationen zu verwenden und gcc_unreachable() zu verwenden, ohne Erfolg.Der generierte Code prüft immer, ob die Variable innerhalb des Bereichs liegt, fügt eine sinnlose Verzweigungsbedingung hinzu und entfernt den Sprungtabellen-Berechnungscode.

Notiz:Da dies in der innersten Schleife eines Decoders geschieht, ist die Leistung von entscheidender Bedeutung.

Es scheint, dass ich nicht der bin nur eins.

Es gibt keine Möglichkeit, GCC zu sagen, dass der Standardzweig niemals eingenommen wird, obwohl er den Standardzweig weglassen kann, wenn er nachweisen kann, dass der Wert niemals außerhalb der Bereiche basierend auf früheren bedingten Schecks ist.

Wie können Sie also gcc dabei helfen, zu beweisen, dass die Variable passt und es im obigen Beispiel keinen Standardzweig gibt?(Natürlich ohne das Hinzufügen eines bedingten Zweigs.)

Aktualisierung

  1. Dies geschah unter OS

  2. Die Funktionen im Beispiel dienen der besseren Lesbarkeit. Denken Sie, dass es sich dabei um Inline-Funktionen oder nur um Anweisungen handelt.Ein Funktionsaufruf auf x86 durchzuführen ist teuer.

    Auch das Beispiel gehört, wie im Hinweis erwähnt, in eine Datenschleife (Big Data).

    Der mit gcc 4.2/OS X generierte Code lautet:

    [...]
    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 darin cmp $7, %eax; ja L11;

  3. OK, ich entscheide mich für die hässliche Lösung und füge einen Sonderfall für gcc-Versionen unter 4.4 hinzu, indem ich eine andere Version ohne Schalter verwende und die &&label-Erweiterungen von goto und gcc verwende.

    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 Label-Array statisch ist und daher nicht bei jedem Aufruf berechnet wird.

War es hilfreich?

Lösung

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


 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?

Andere Tipps

Vielleicht könnten Sie anstelle 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;
}

Haben Sie versucht, das zu deklarieren? 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 ) {
...
}

Hoffe, das funktioniert!

Ich habe es nicht versucht, bin mir aber nicht sicher gcc_unreachable macht das Gleiche wie __builtin_unreachable.Googelt die beiden, gcc_unreachable scheint als Assertionstool für die Entwicklung von GCC selbst konzipiert zu sein, möglicherweise mit einem Hinweis zur Verzweigungsvorhersage __builtin_unreachable macht das Programm sofort undefiniert – was sich anhört, als würde man den Grundblock löschen, was Sie wollen.

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

Vielleicht verwenden Sie einfach a default Bezeichnung für den ersten oder letzten Fall?

Diese Frage ist unter dem Gesichtspunkt einer für uns scheinbar offensichtlichen fehlenden Compiler-Optimierung sicherlich interessant, und ich habe viel Zeit damit verbracht, eine einfache Lösung zu finden, größtenteils aus persönlicher Neugier.

Trotzdem muss ich zugeben Ich bin sehr skeptisch, dass diese zusätzliche Anweisung jemals zu einem messbaren Leistungsunterschied führen wird in der Praxis, insbesondere auf einem neuen Mac.Wenn Sie über eine beträchtliche Datenmenge verfügen, sind Sie an E/A gebunden und eine einzelne Anweisung wird niemals Ihr Engpass sein.Wenn Sie über eine kleine Datenmenge verfügen, müssen Sie eine durchführen viel, viel, viel Es müssen mehrere Berechnungen wiederholt werden, bevor eine einzelne Anweisung zum Engpass wird.

Würden Sie Code veröffentlichen, um zu zeigen, dass es tatsächlich einen Leistungsunterschied gibt?Oder beschreiben Sie den Code und die Daten, mit denen Sie arbeiten?

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top