Neue Optimierungen für x86 im kommenden GCC 5.0

    Die tatsächliche Entwicklung neuer Optimierungen in GCC 5.0 kann daher als abgeschlossen betrachtet werden. Das Produkt GCC 5.0 befindet sich derzeit in der Phase 3 , dh die Optimierung wird bereits abgeschlossen. In diesem und den folgenden Artikeln werde ich auf Optimierungen eingehen, die in GCC 5.0 für x86 implementiert sind, und auf deren Auswirkungen auf die Programmleistung für Intel Atom- und Intel Core-Prozessoren . Heute konzentrieren wir uns auf die Vektorisierung von Gruppenspeicherzugriffen. In den folgenden Artikeln werde ich auf Beschleunigungen im 32-Bit-PIC-Modus und eine zusätzliche Verstärkung der Vektorisierung eingehen.

    Wie Sie wahrscheinlich bereits vermutet haben, wurde in GCC 5.0 die Vektorisierung von Gruppenspeicherzugriffen erheblich verbessert. Der Gruppenspeicherzugriff bezieht sich auf eine iterierbare Abfolge von Aufrufen. Zum Beispiel:

    x = a[i], y = a[i + 1], z = a[i + 2]

    Über i kann eine Gruppe von Speicherdownloads der Länge 3 iteriert werden . Die Länge der Gruppe von Speicherzugriffen ist der Abstand zwischen der niedrigsten und der höchsten Adresse in der Gruppe. Im obigen Beispiel ist dies (i + 2) - (i) + 1 = 3. Die
    Anzahl der Speicherzugriffe in der Gruppe überschreitet ihre Länge nicht. Zum Beispiel:

    x = a[i], z = a[i + 2]

    iterierbar über i ist eine Gruppe der Länge 3, obwohl es nur 2 Speicherzugriffe gibt.

    GCC 4.9 vektorisiert Gruppen, bei denen die Länge eine Potenz von 2 (2, 4, 8 ...) ist.

    GCC 5.0 vektorisiert Gruppen mit einer Länge von 3 oder Grad 2 (2, 4, 8 ...). Andere Längen werden nicht vektorisiert, da sie in realen Anwendungen sehr selten sind.

    Am häufigsten wird die Vektorisierung einer Gruppe von Speicherzugriffen verwendet, wenn mit Arrays von Strukturen gearbeitet wird.

    1. Bildkonvertierung zB in der RGB-Struktur. Ein Beispiel .
    2. Arbeiten Sie mit N-dimensionalen Koordinaten (um beispielsweise dreidimensionale Punkte zu normalisieren). Ein Beispiel .
    3. Multiplikation von Vektoren mit einer konstanten Matrix:

    a[i][0] = 7 * b[i][0] - 3 * b[i][1];
    a[i][1] = 2 * b[i][0] + b[i][1];
    

    Insgesamt in GCC 5.0 (im Vergleich zu 4.9)

    • Es wurde eine Gruppe von Speicherzugriffen der Länge 3 vektorisiert
    • Deutlich verbesserte Vektorisierung einer Gruppe von Speicherdownloads für jede Länge
    • Die Techniken zur Vektorisierung von Speicherzugriffsgruppen, die für einen bestimmten x86-Prozessor optimal sind, wurden eingesetzt.

    Die folgenden Tabellen enthalten Schätzungen zur Leistungssteigerung bei Verwendung von GCC 5.0 für Byte-Strukturen (die größte Anzahl von Elementen in einem Vektor) im Vergleich zu GCC 4.9. Der folgende Zyklus wird zur Auswertung herangezogen:

      int i, j, k;  
      byte *in = a, *out = b; 
      for (i = 0; i < 1024; i++) 
        { 
          for (k = 0; k < STGSIZE; k++) 
            { 
              byte s = 0; 
              for (j = 0; j < LDGSIZE; j++) 
                s += in[j] * c[j][k]; 
              out[k] = s; 
            } 
          in += LDGSIZE; 
          out += STGSIZE; 
        } 
    

    Wo:
    • c ist eine konstante Matrix:

    
    const byte c[8][8] = {1, -1, 1, -1, 1, -1, 1, -1, 
                          1, 1, -1, -1, 1, 1, -1, -1, 
                          1, 1, 1, 1, -1, -1, -1, -1, 
                          -1, 1, -1, 1, -1, 1, -1, 1, 
                          -1, -1, 1, 1, -1, -1, 1, 1, 
                          -1, -1, -1, -1, 1, 1, 1, 1, 
                          -1, -1, -1, 1, 1, 1, -1, 1, 
                          1, -1, 1, 1, 1, -1, -1, -1}; 
    

    Eine solche Matrix wird verwendet, um Berechnungen innerhalb der Schleife auf relativ schnelle Additionen und Subtraktionen zu minimieren.
    • In und Out sind Zeiger auf die globalen Arrays "a [1024 * LDGSIZE]" und "b [1024 * STGSIZE]".
    • Byte ist ein vorzeichenloses Zeichen
    • LDGSIZE und STGSIZE sind Makros, die die Länge einer Gruppe von Speicherdownloads und Speichern im Speicher bestimmen


    Kompilierungsoptionen "-Ofast" plus "-march = slm" für Silvermont , "-march = core-avx2" für Haswell und alle Kombinationen -DLDGSIZE = {1,2,3,4,8} -DSTGSIZE = {1,2 , 3,4,8}

    GCC 5.0 Leistungssteigerung im Vergleich zu 4,9 (wie oft es beschleunigt, je mehr desto besser).

    Silbermont : Intel Atom (TM) CPU C2750 bei 2,41 GHz

    Bis zu 6,5-fache Steigerung!

    Bild

    Wie aus der Tabelle ersichtlich ist, sind die Ergebnisse für Speichergruppen der Länge 3 nicht sehr gut. Dies liegt daran, dass eine solche Konvertierung 8 "pshufb" -Anweisungen erfordert, deren Ausführungszeit auf Silvermont etwa 5 Taktzyklen beträgt. Trotzdem kann die Vektorisierung anderer Teams in der Schleife zu einer größeren Steigerung führen. Dies ist am Beispiel einer Gruppe von Speicherdownloads der Länge 2, 3, 4 und 8 zu sehen.

    Beispiel für einen GCC-Assembler für eine Speicherladegruppe der Länge 2:
    GCC 4.9Gcc 5.0
    movdqa .LC0 (% rip),% xmm7
    movdqa .LC1 (% rip),% xmm6
    movdqa .LC2 (% rip),% xmm5
    movdqa .LC3 (% rip),% xmm4
    movdqu a (% rax,% rax), % xmm1
    movdqu a + 16 (%
    rax,% rax ),% xmm0 movdqa% xmm1,% xmm3
    pshufb% xmm7,% xmm3
    movdqa% xmm0,% xmm2
    pshufb% xmm5,% xmm1
    pshufb% xmm6,% xmm2
    pshufb % xmm0
    por% xmm2,% xmm3
    por% xmm0,% xmm2
    movdqa .LC0(%rip), %xmm3
    movdqu a(%rax,%rax), %xmm0
    movdqu a+16(%rax,%rax), %xmm1
    movdqa %xmm3, %xmm4
    pand %xmm0, %xmm3
    psrlw $8, %xmm0
    pand %xmm1, %xmm4
    psrlw $8, %xmm1
    packuswb %xmm4, %xmm3
    packuswb %xmm1, %xmm0
    Hier, wie im Beispiel für Haswell unten , ist es erwähnenswert, dass die meisten ".LC" -Konstanten Invarianten in der Schleife sind, aber nur, wenn es freie Register gibt. Andernfalls müssen sie direkt in phufb installiert werden: "pshufb .LC0,% xmm3". Ein solches pshufb wird um die Größe der Adresse größer und möglicherweise länger dauern, bis es ausgeführt wird.

    Haswell : Intel Core (TM) i7-4770K-CPU bei 3,50 GHz

    Bis zu dreifache Steigerung!

    Bild

    In diesem Fall sind die Ergebnisse für Speichergruppen der Länge 3 viel besser, da auf Haswell der Befehl "pshufb" in einem Taktzyklus ausgeführt wird. Darüber hinaus ist hier die größte Zunahme für die eingebettete Vektorisierung von Speicherzugriffsgruppen der Länge 3 zu verzeichnen.

    Ein Beispiel für einen GCC-Assembler für eine Speicherladegruppe der Länge 2 ist:
    GCC 4.9Gcc 5.0
    vmovdqa .LC0(%rip), %ymm7
    vmovdqa .LC1(%rip), %ymm6
    vmovdqa .LC2(%rip), %ymm5
    vmovdqa .LC3(%rip), %ymm4
    vmovdqu a(%rax,%rax), %ymm0
    vmovdqu a+32(%rax,%rax), %ymm2
    vpshufb %ymm7, %ymm0, %ymm1
    vpshufb %ymm5, %ymm0, %ymm0
    vpshufb %ymm6, %ymm2, %ymm3
    vpshufb %ymm4, %ymm2, %ymm2
    vpor %ymm3, %ymm1, %ymm1
    vpor %ymm2, %ymm0, %ymm0
    vpermq $216, %ymm1, %ymm1
    vpermq $216, %ymm0, %ymm0
    vmovdqa .LC0(%rip), %ymm3
    vmovdqu a(%rax,%rax), %ymm0
    vmovdqu a+32(%rax,%rax), %ymm2
    vpand %ymm0, %ymm3, %ymm1
    vpsrlw $8, %ymm0, %ymm0
    vpand %ymm2, %ymm3, %ymm4
    vpsrlw $8, %ymm2, %ymm2
    vpackuswb %ymm4, %ymm1, %ymm1
    vpermq $216, %ymm1, %ymm1
    vpackuswb %ymm2, %ymm0, %ymm0
    vpermq $216, %ymm0, %ymm0
    Из всего вышесказанного следует вывод: используя GCC 5.0, можно существенно ускорить производительность приложений, подобных описанным выше. Начинать пробовать можно уже сейчас! Большинство правок планируется портировать в Android NDK.

    Компиляторы используемые в замерах:

    Sie können das Beispiel, an dem Messungen vorgenommen wurden, aus dem englischen Originaltext des Artikels herunterladen .

    Jetzt auch beliebt: