2016-08-03 31 views
6

Versuchen, mit meiner Idee, die Verwendung von Hardware- und Hardware-Speicherbarrieren fortzusetzen, die Out-of-Order-Optimierung für eine bestimmte Funktion in einem Code deaktivieren, der mit Compiler-Optimierung kompiliert wird und damit Software Semaphore unter Verwendung von Algorithmen wie Peterson oder Deker, die keine out-of-Order-Ausführung erfordert, habe ich den folgenden Code getestet, die sowohl Barriere asm volatile("": : :"memory") und gcc SW enthält Barriere gebautet HW implementieren konnte ich __sync_synchronize:Verwenden von Speicherbarrieren, um die Ausführung in der richtigen Reihenfolge zu erzwingen

#include <stdio.h> 
int main(int argc, char ** argv) 
{ 
    int x=0; 
    asm volatile("": : :"memory"); 
    __sync_synchronize(); 
    x=1; 
    asm volatile("": : :"memory"); 
    __sync_synchronize(); 
    x=2; 
    asm volatile("": : :"memory"); 
    __sync_synchronize(); 
    x=3; 
    printf("%d",x); 
    return 0; 
} 

Aber die Kompilierungsausgabedatei ist:

main: 
.LFB24: 
    .cfi_startproc 
    subq $8, %rsp 
    .cfi_def_cfa_offset 16 
    mfence 
    mfence 
    movl $3, %edx 
    movl $.LC0, %esi 
    movl $1, %edi 
    xorl %eax, %eax 
    mfence 
    call __printf_chk 
    xorl %eax, %eax 
    addq $8, %rsp 

Und wenn ich die Barrieren entfernen und wieder kompilieren, erhalte ich:

main 
.LFB24: 
    .cfi_startproc 
    subq $8, %rsp 
    .cfi_def_cfa_offset 16 
    movl $3, %edx 
    movl $.LC0, %esi 
    movl $1, %edi 
    xorl %eax, %eax 
    call __printf_chk 
    xorl %eax, %eax 
    addq $8, %rsp 

beide kompiliert mit gcc -Wall -O2 in Ubuntu 14.04.1 LTS, x86.

Das erwartete Ergebnis war, dass die Ausgabedatei des Codes, der die Speicherbarrieren enthält, alle Zuordnungen der Werte enthält, die ich in meinem Quellcode habe, mit mfence zwischen ihnen.

Nach einem verwandten Stackoverflow Post -

gcc memory barrier __sync_synchronize vs asm volatile("": : :"memory")

Wenn Ihre Inline-Assembler bei jeder Iteration das Hinzufügen, gcc ist nicht erlaubt, die Reihenfolge der Operationen über die Barriere zu ändern

Und später:

Howe Wenn die CPU diesen Code ausführt, ist es erlaubt, die Operationen "unter der Haube" neu zu ordnen, solange sie das Speichermodell nicht unterbrechen. Dies bedeutet, dass die Durchführung der Operationen außerhalb der Reihenfolge ausgeführt werden kann (wenn die CPU dies unterstützt, wie es die meisten heutzutage tun). Ein HW Zaun hätte das verhindert.

Aber wie Sie sehen können, ist der einzige Unterschied zwischen dem Code mit den Speicherbarrieren und den Code ohne sie ist, dass der frühere enthält mfence in einer Weise, die ich nicht sehen erwartet wurde, und nicht alle Zuordnungen sind inklusive.

Warum ist die Ausgabedatei der Datei mit den Speicherbarrieren nicht wie erwartet - Warum wurde die mfence Reihenfolge geändert? Warum hat der Compiler einige der Aufgaben entfernt? Darf der Compiler solche Optimierungen vornehmen, selbst wenn die Speicherbarriere angewendet wird und jede einzelne Codezeile trennt?

Verweise auf die Speicherbarrieretypen und Nutzung:

+1

Terminologie: ** Out-of-Order-Ausführung ist getrennt von Speicher-Neuordnung **. Sogar CPUs in der richtigen Reihenfolge sind pipelined und profitieren von einem Speicherpuffer, insbesondere für Filialen, die in L1 fehlen. (https://en.wikipedia.org/wiki/MESI_protocol#Memory_Barriers. Sobald bekannt ist, dass sie nicht spekulativ sind, können sie nur durch Speicherordnungslogik verfolgt werden (um die StoreStore- und LoadStore-Anordnung bei Bedarf durchzusetzen), bis sie tatsächlich funktionieren Commit zu L1-Cache, nachdem die Pipeline sie vergessen hat.) 'MFENCE' serialisiert die Pipeline nicht; Es serialisiert nur die Reihenfolge, in der Speichervorgänge global sichtbar werden. –

Antwort

4

Die Speicherbarrieren sagen dem Compiler/CPU, die Anweisung sollten nicht über die Schranke nachbestellt werden, sie bedeuten nicht, dass Schreibvorgänge, die nachweislich sinnlos sind, sowieso gemacht werden müssen.

Wenn Sie definieren Ihre x als volatile, kann der Compiler nicht die Annahme macht, dass es die einzige Entität ist, die über x s Wert pflegt und hat die Regeln der C abstrakten Maschine zu folgen, die für den Speicherschreib ist tatsächlich passieren.

In Ihrem konkreten Fall könnten Sie dann die Barrieren überspringen, denn es ist bereits sichergestellt, dass flüchtige Zugriffe nicht neu geordnet werden.

Wenn Sie C11-Unterstützung haben, sind Sie besser dran mit _Atomic s, die zusätzlich garantieren kann, dass normale Zuordnungen nicht gegen Ihre x neu geordnet werden und dass die Zugriffe atomar sind.


EDIT: GCC (sowie Klappern) scheinen in dieser Hinsicht als unvereinbar und wird diese optimizaton nicht immer tun. I opened a GCC bug report regarding this.

+3

Sie haben eine viel bessere Antwort geschrieben als ich. – 2501

+0

Richtige Antwort. Ich habe es jetzt mit "volatile" getestet, und der Code mit der Speicherbarriere wurde genau so kompiliert, wie ich es erwartet hatte (während der Code ohne die Speicherbarriere noch ein wenig optimiert wurde). Leider kann ich das "atomare" nicht testen, da ich keine C11-Unterstützung habe. – user2162550

+0

@ 2501 Danke. Fühlen Sie sich frei, es zu erweitern, wenn Sie denken, dass etwas verbessert werden könnte. :) – a3f