2016-07-23 23 views
0

Beim Programmieren mit Intrinsics kam das folgende Problem auf. Wenn ich eine lokale Variable in einer Inline-Funktion laden oder speichern will, habe ich einen Speicherverletzungsfehler erhalten, aber nur wenn die Funktion inline ist. Ich habe keine Ahnung, warum in der Inline-Funktion die Stack-Variablen nicht ausgerichtet sind.Lokale Variable in der Inline-Funktion nicht ausgerichtet

Ich habe das mit vielen verschiedenen Versionen von GCC 4.9, 5.3, 6.1 getestet.

Beispiel des fehlgeschlagen:

static inline foo(double *phi){ 
    double localvar[4]; 
    __m256d var = _mm256_load_pd (phi); 
    __m256d res = _mm256_mul_pd(var, var); 
    _mm256_store_pd (localvar, res); // <- failed due to memory violation 
    ... 
} 

Wenn ich __attribute__ ((aligned (32))) hinzufügen oder entfernen inline dann funktioniert die Funktion, wie es sollte.

So kann mir jemand erklären (bitte im Detail), warum lokale Variablen im Allgemeinen ausgerichtet sind ohne __attribute__ ((aligned (32))) hinzuzufügen und lokale Variablen in Inline-Funktion nicht?

+2

Durch Zufall, schätze ich. – MikeCAT

+0

Bitte versuchen Sie [diesen Code] (http://www.tutorialspoint.com/compile_c_online.php?PID=0Bw_CjBb95KQMNFRDcGd3NmxDQmc) mit zusätzlichen lokalen Variablen in Ihrer Umgebung. Wird das Array immer noch ausgerichtet sein? – MikeCAT

+1

Standard C erfordert keine Ausrichtung von lokalen Variablen. Sie müssen Compiler-spezifische Erweiterungen wie die von Ihnen erwähnte verwenden, um das gewünschte Ergebnis zu erhalten. –

Antwort

2

Bereitstellung von 32-Byte-Ausrichtung kostet zusätzliche Anweisungen (weil der ABI nur 16-Byte-Ausrichtung garantiert; sehen Sie sich die ASM für die Version mit alignas(32) oder __attribute__((aligned(32))) an). Natürlich tut der Compiler es nicht, wenn Sie nicht danach fragen, weil es nicht frei ist. (Siehe auch gcc's -mpreferred-stack-boundary which controls this, und das Tag-Wiki für Links zu ABI-Dokumenten).

double localvar[4]; muss nur 8-Byte ausgerichtet sein, damit jedes Element natürlich ausgerichtet ist. Die SysV x86-64 ABI garantiert eine 16-Byte-Ausrichtung für C99-Arrays variabler Größe. Ich bin mir nicht sicher, ob normale Kompilierzeit-Konstanten große Arrays 16-B-Ausrichtung standardmäßig erhalten oder nicht.

Aktuelle Versionen von gcc jedoch aus irgendeinem Grund den Stack auf 32B in einer Testfunktion ausrichten, die __m256d lokale Variablen hat. Bei -O3 verschüttet es sie nicht auf den Stapel, also sind sie verschwendet (abgesehen davon, dass Buggy-Code wie dieser funktioniert). Die Tatsache, dass gcc dieses Zeug nicht entfernt, ist eine verpasste Optimierung. (Es wird bei -O0 benötigt, wo gcc alles zum Speicher verschüttet.)

Da meine Version Ihrer Testfunktion (die tatsächlich kompiliert) keine anderen Einheimischen hat, ist das Array der Doppelgänger auch 32B-ausgerichtet. Vermutlich gliedern Sie es in einen Aufrufer, der andere Einheimische hat, und das führt zu einer anderen Ausrichtung für das Array.

Here's the code on the Godbolt compiler explorer:

extern void use_buffer(double*); 
// static inline 
void no_alignment(const double *phi){ 
    double localvar[4]; 
    __m256d var = _mm256_load_pd (phi); 
    __m256d res = _mm256_mul_pd(var, var); 
    _mm256_storeu_pd (localvar, res);   // use an unaligned store since we didn't request alignment for the buffer 
    use_buffer(localvar); 
} 

    lea  r10, [rsp+8]     // save old RSP (in a clumsy way) 
    and  rsp, -32      // truncate RSP to the next 32B boundary 
    push QWORD PTR [r10-8]   // save more stuff 
    push rbp 
    mov  rbp, rsp 
    push r10 
    sub  rsp, 40 
    ...   vmovupd YMMWORD PTR [rbp-48], ymm0  ... // function body 
    add  rsp, 40 
    pop  r10 
    pop  rbp 
    lea  rsp, [r10-8] 

Aus diesem Grund ist der Code, wenn es inlined nicht zur Arbeit passiert. Obwohl es merkwürdig ist, dass es auch ohne das inline Schlüsselwort nicht inline wird, es sei denn Sie kompilierten ohne Optimierung oder Sie verwendeten static nicht, um dem Compiler mitzuteilen, dass eine separate Definition nicht benötigt wurde.

2

_mm256_store_pd erfordert, dass die Speicheradresse, auf der Sie speichern, an einer 32-Byte-Grenze ausgerichtet sein muss. In C denke ich nur, dass die Standardausrichtung für und 8 Byte doppelt eine 8-Byte-Grenze ist.

Wenn ich raten musste, wenn die Funktion nicht inline ist, startet das Localvar-Array auf einer 32-Byte-Grenze. Ich bin mir nicht sicher, ob dies eine Garantie oder nur Glück ist. Ich rate Glück, weil das Inline-Setzen einer Funktion in der Theorie nichts ändern sollte. Der Compiler kann gerade die richtige Anzahl von Bytes auf den Stapel schieben, so dass er ausgerichtet wird. Ich sehe auch keinen Grund, warum es eine 32-Byte-Ausrichtung garantieren würde.

Wenn es inline ist, würde es so tun, als ob der Code gerade eingegeben wurde, wo Sie die Funktion aufrufen. Daher wird nur garantiert, dass localvar 8 Byte ausgerichtet ist und nicht die garantierte 32 Byte Ausrichtung. Ich denke, die richtige Lösung ist, das ausgerichtete Attribut zu verwenden, das dein Problem löst. Sie könnten auch die _mm256_storeu_pd intrinsische verwenden, die die gleiche Sache ohne die Ausrichtung erfordert. Aus meiner Erfahrung mit meiner haswell CPU ist es genauso schnell.

+0

Es ist pures Glück, dass es ohne "Inline" funktioniert. x86-64-ABIs halten den Stapel 16B normalerweise auf Funktionsaufrufen ausgerichtet. 'storeu' ist eine gute Option für ein kleines Vektorfeld mit einem Vektor, aber wenn Sie ein größeres Scratch-Array haben, ist es günstig, es zur Laufzeit auszurichten. Dies vermeidet Cache-Zeilen-Splits und hilft möglicherweise bei der Speicherweiterleitung (ich vergesse). –

+0

Update, stellt sich heraus, es ist nicht nur reines Glück. Es hängt mit einer verpassten Optimierung in gcc zusammen. –