2012-09-25 26 views
5

Bezug nehmend auf die Antwort @auselen hier: Using ARM NEON intrinsics to add alpha and permute, sieht aus wie Armcc Compiler ist viel besser als der GCC-Compiler für NEON-Optimierungen. Ist das wirklich wahr? Ich habe Armcompiler nicht wirklich ausprobiert. Aber ich habe ziemlich optimierten Code mit dem GCC-Compiler mit -O3-Optimierungs-Flag. Aber jetzt frage ich mich, ob Armcc wirklich so gut ist? Also welcher der beiden Compiler ist besser, wenn man alle Faktoren berücksichtigt?Welcher ist besser, gcc oder armcc für NEON-Optimierungen?

+5

Die NEON-Unterstützung in gcc ist weniger ausgereift als die skalare integer/fp-Unterstützung. Auselens Vergleich basiert jedoch auf gcc 4.4.3, veröffentlicht vor über 2,5 Jahren. Ein gutes Stück Arbeit wurde seither in Verbesserungen des NEONs investiert. Zur gleichen Zeit ist Armcc 5.01 nur ein Jahr alt. Während ich immer noch erwarten würde, dass Armcc 5.02 vorne liegt, wäre ein relevanterer Vergleich zwischen ihm und einem 4.7 gcc. – unixsmurf

+1

@unixsmurf eins von +1 ist von mir :) – auselen

Antwort

7

Compiler sind auch Software, sie neigen dazu, im Laufe der Zeit zu verbessern. Jede generische Behauptung wie armcc ist besser als GCC on NEON (oder besser gesagt: Vektorisierung) kann nicht für immer gelten, da eine Entwicklergruppe die Lücke mit ausreichender Aufmerksamkeit schließen kann. Es ist jedoch logisch, Compiler zu erwarten, die von Hardware-Herstellern entwickelt wurden, weil sie diese Funktionen demonstrieren/vermarkten müssen.

Ein aktuelles Beispiel, das ich gesehen habe, war hier auf Stack Overflow über eine answer for branch prediction. Zitieren aus der letzten Zeile des aktualisierten Abschnitts "Dies zeigt, dass selbst reife moderne Compiler in ihrer Fähigkeit, Code zu optimieren, stark variieren können ...".

Ich bin ein großer Fan von GCC, aber ich würde nicht auf die Qualität des Codes gegen Compiler von Intel oder ARM wetten.Ich erwarte, dass jeder kommerzielle Mainstream-Compiler einen Code produziert, der mindestens so gut ist wie GCC. Eine empirische Antwort auf diese Frage könnte hilbert-space's neon optimization example sein und sehen, wie verschiedene Compiler es optimieren.

void neon_convert (uint8_t * __restrict dest, uint8_t * __restrict src, int n) 
{ 
    int i; 
    uint8x8_t rfac = vdup_n_u8 (77); 
    uint8x8_t gfac = vdup_n_u8 (151); 
    uint8x8_t bfac = vdup_n_u8 (28); 
    n/=8; 

    for (i=0; i<n; i++) 
    { 
    uint16x8_t temp; 
    uint8x8x3_t rgb = vld3_u8 (src); 
    uint8x8_t result; 

    temp = vmull_u8 (rgb.val[0],  rfac); 
    temp = vmlal_u8 (temp,rgb.val[1], gfac); 
    temp = vmlal_u8 (temp,rgb.val[2], bfac); 

    result = vshrn_n_u16 (temp, 8); 
    vst1_u8 (dest, result); 
    src += 8*3; 
    dest += 8; 
    } 
} 

Dies ist armcc 5,01

20: f421140d vld3.8 {d1-d3}, [r1]! 
    24: e2822001 add r2, r2, #1 
    28: f3810c04 vmull.u8 q0, d1, d4 
    2c: f3820805 vmlal.u8 q0, d2, d5 
    30: f3830806 vmlal.u8 q0, d3, d6 
    34: f2880810 vshrn.i16 d0, q0, #8 
    38: f400070d vst1.8 {d0}, [r0]! 
    3c: e1520003 cmp r2, r3 
    40: bafffff6 blt 20 <neon_convert+0x20> 

Dies ist GCC 4.4.3-4.7.1

1e: f961 040d vld3.8 {d16-d18}, [r1]! 
    22: 3301  adds r3, #1 
    24: 4293  cmp r3, r2 
    26: ffc0 4ca3 vmull.u8 q10, d16, d19 
    2a: ffc1 48a6 vmlal.u8 q10, d17, d22 
    2e: ffc2 48a7 vmlal.u8 q10, d18, d23 
    32: efc8 4834 vshrn.i16 d20, q10, #8 
    36: f940 470d vst1.8 {d20}, [r0]! 
    3a: d1f0  bne.n 1e <neon_convert+0x1e> 

die extrem ähnlich sieht, so haben wir ein Unentschieden. Nachdem ich das gesehen habe, habe ich versucht, Alpha hinzuzufügen und wieder zu permutieren.

void neonPermuteRGBtoBGRA(unsigned char* src, unsigned char* dst, int numPix) 
{ 
    numPix /= 8; //process 8 pixels at a time 

    uint8x8_t alpha = vdup_n_u8 (0xff); 

    for (int i=0; i<numPix; i++) 
    { 
     uint8x8x3_t rgb = vld3_u8 (src); 
     uint8x8x4_t bgra; 

     bgra.val[0] = rgb.val[2]; //these lines are slow 
     bgra.val[1] = rgb.val[1]; //these lines are slow 
     bgra.val[2] = rgb.val[0]; //these lines are slow 

     bgra.val[3] = alpha; 

     vst4_u8(dst, bgra); 

     src += 8*3; 
     dst += 8*4; 
    } 
} 

mit gcc kompilieren ...

$ arm-linux-gnueabihf-gcc --version 
arm-linux-gnueabihf-gcc (crosstool-NG linaro-1.13.1-2012.05-20120523 - Linaro GCC 2012.05) 4.7.1 20120514 (prerelease) 
$ arm-linux-gnueabihf-gcc -std=c99 -O3 -c ~/temp/permute.c -marm -mfpu=neon-vfpv4 -mcpu=cortex-a9 -o ~/temp/permute_gcc.o 

00000000 <neonPermuteRGBtoBGRA>: 
    0: e3520000 cmp r2, #0 
    4: e2823007 add r3, r2, #7 
    8: b1a02003 movlt r2, r3 
    c: e92d01f0 push {r4, r5, r6, r7, r8} 
    10: e1a021c2 asr r2, r2, #3 
    14: e24dd01c sub sp, sp, #28 
    18: e3520000 cmp r2, #0 
    1c: da000019 ble 88 <neonPermuteRGBtoBGRA+0x88> 
    20: e3a03000 mov r3, #0 
    24: f460040d vld3.8 {d16-d18}, [r0]! 
    28: eccd0b06 vstmia sp, {d16-d18} 
    2c: e59dc014 ldr ip, [sp, #20] 
    30: e2833001 add r3, r3, #1 
    34: e59d6010 ldr r6, [sp, #16] 
    38: e1530002 cmp r3, r2 
    3c: e59d8008 ldr r8, [sp, #8] 
    40: e1a0500c mov r5, ip 
    44: e59dc00c ldr ip, [sp, #12] 
    48: e1a04006 mov r4, r6 
    4c: f3c73e1f vmov.i8 d19, #255 ; 0xff 
    50: e1a06008 mov r6, r8 
    54: e59d8000 ldr r8, [sp] 
    58: e1a0700c mov r7, ip 
    5c: e59dc004 ldr ip, [sp, #4] 
    60: ec454b34 vmov d20, r4, r5 
    64: e1a04008 mov r4, r8 
    68: f26401b4 vorr d16, d20, d20 
    6c: e1a0500c mov r5, ip 
    70: ec476b35 vmov d21, r6, r7 
    74: f26511b5 vorr d17, d21, d21 
    78: ec454b34 vmov d20, r4, r5 
    7c: f26421b4 vorr d18, d20, d20 
    80: f441000d vst4.8 {d16-d19}, [r1]! 
    84: 1affffe6 bne 24 <neonPermuteRGBtoBGRA+0x24> 
    88: e28dd01c add sp, sp, #28 
    8c: e8bd01f0 pop {r4, r5, r6, r7, r8} 
    90: e12fff1e bx lr 

mit armcc Kompilieren ...

$ armcc 
ARM C/C++ Compiler, 5.01 [Build 113] 
$ armcc --C99 --cpu=Cortex-A9 -O3 -c permute.c -o permute_arm.o 

00000000 <neonPermuteRGBtoBGRA>: 
    0: e1a03fc2 asr r3, r2, #31 
    4: f3870e1f vmov.i8 d0, #255 ; 0xff 
    8: e0822ea3 add r2, r2, r3, lsr #29 
    c: e1a031c2 asr r3, r2, #3 
    10: e3a02000 mov r2, #0 
    14: ea000006 b 34 <neonPermuteRGBtoBGRA+0x34> 
    18: f420440d vld3.8 {d4-d6}, [r0]! 
    1c: e2822001 add r2, r2, #1 
    20: eeb01b45 vmov.f64 d1, d5 
    24: eeb02b46 vmov.f64 d2, d6 
    28: eeb05b40 vmov.f64 d5, d0 
    2c: eeb03b41 vmov.f64 d3, d1 
    30: f401200d vst4.8 {d2-d5}, [r1]! 
    34: e1520003 cmp r2, r3 
    38: bafffff6 blt 18 <neonPermuteRGBtoBGRA+0x18> 
    3c: e12fff1e bx lr 

In diesem Fall armcc produziert viel besseren Code. Ich denke, dies rechtfertigt . Die meiste Zeit produziert GCC gut genug Code, aber Sie sollten kritische Teile im Auge behalten oder, am wichtigsten, zuerst müssen Sie messen/profilieren.

+1

Probieren Sie '-marm' Flag in GCC, Daumen Code ist nicht so alt in GCC als noch, noch mehr für thumb2 Einheit in Cortex-A9. – sgupta

+1

@ user1075375 aktualisiert. – auselen

+2

Hmm, wie erwartet, register verschütten ist ziemlich umfangreich mit gcc. – sgupta

7

Wenn Sie NEON-Intrinsics verwenden, sollte der Compiler nicht so wichtig sein. Die meisten (wenn nicht alle) NEON-Intrinsics werden in einen einzelnen NEON-Befehl übersetzt, so dass dem Compiler nur die Registerzuweisung und die Befehlsplanung überlassen bleibt. Meiner Erfahrung nach sind sowohl GCC 4.2 als auch Clang 3.1 bei diesen Aufgaben recht gut.

Beachten Sie jedoch, dass die NEON-Anweisungen etwas ausdrucksstärker sind als die NEON-Intrinsics. Zum Beispiel haben NEON-Lade-/Speicherbefehle Pre- und Post-Inkrement-Adressierungsmodi, die ein Laden oder Speichern mit einem Inkrement des Adressregisters kombinieren, wodurch Sie eine Anweisung sparen. Die NEON-Eigenarten stellen keine explizite Möglichkeit dar, dies zu tun, sondern verlassen sich stattdessen auf den Compiler, um ein regulatorisches NEON-Laden/Speichern-Intrinsic und ein Adresseninkrement in einen Lade-/Speicherbefehl mit Post-Inkrement zu kombinieren. In ähnlicher Weise können Sie mit einigen Lade-/Speicherbefehlen die Ausrichtung der Speicheradresse angeben und schneller ausführen, wenn Sie strengere Ausrichtungsgarantien angeben. Die NEON-Eigenarten erlauben Ihnen wiederum nicht, die Ausrichtung explizit anzugeben, sondern verlassen sich stattdessen auf den Compiler, um den korrekten Ausrichtungsbezeichner abzuleiten. Theoretisch verwenden Sie "align" -Attribute auf Ihren Zeigern, um dem Compiler geeignete Hinweise zu geben, aber zumindest scheint Clang diese zu ignorieren ...

Nach meiner Erfahrung sind weder Clang noch GCC sehr hell diese Arten von Optimierungen. Glücklicherweise ist der zusätzliche Leistungsvorteil dieser Art von Optimierung normalerweise nicht so hoch - es ist mehr wie 10% als 100%.

Ein weiterer Bereich, in dem diese beiden Compiler nicht besonders intelligent sind, ist die Vermeidung von Stack-Verschüttungen. Wenn Sie mehr vektorwertige Variablen als NEON-Register verwenden, scheinen beide Compiler schrecklichen Code zu erzeugen. Im Grunde scheinen sie Anweisungen zu planen, die auf der Annahme basieren, dass genügend Register verfügbar sind. Die Registerzuweisung scheint danach zu kommen und scheint einfach Werte auf den Stapel zu verschütten, sobald sie von Registern ausgeführt wird. Stellen Sie also sicher, dass Ihr Code zu jeder Zeit einen Arbeitssatz von weniger als 16 128-Bit-Vektoren oder 32 64-Bit-Vektoren hat!

Insgesamt habe ich ziemlich gute Ergebnisse sowohl von GCC und Clang, aber ich musste regelmäßig den Code ein wenig reorganisieren, um Compiler Idiosynkrasien zu vermeiden. Mein Rat wäre, bei GCC oder Clang zu bleiben, aber überprüfe das regelmäßig mit dem Disassembler deiner Wahl.

Also, insgesamt würde ich sagen, mit GCC kleben ist in Ordnung. Sie sollten sich jedoch die Demontage der leistungskritischen Teile ansehen und prüfen, ob sie vernünftig aussieht.