Die Compiler-Optimierung bricht Multi-Threaded-Code

8

Nachdem Sie auf die harte Tour gelernt haben, dass shared Variablen derzeit nicht von Speicherbarrieren geschützt werden Ich bin jetzt auf ein anderes Problem gestoßen. Entweder mache ich etwas falsch, oder die vorhandene Compiler-Optimierung in dmd kann Multi-Threaded-Code durch Neuordnen von Lesevorgängen von shared Variablen brechen.

Wenn ich zum Beispiel eine ausführbare Datei mit dmd -O (vollständige Optimierung) kompiliere, optimiert der Compiler glücklicherweise die lokale Variable o in diesem Code (wobei cas die Funktion zum Vergleichen und Tauschen von core.atomic )

%Vor%

zu so etwas (siehe Dis-Assembly unten):

%Vor%

Im "optimierten" Code wird cnt zweimal aus dem Speicher gelesen, wodurch das Risiko besteht, dass ein anderer Thread cnt dazwischen geändert hat. Die Optimierung zerstört grundsätzlich den Vergleichs- und Austauschalgorithmus.

Ist das ein Fehler oder gibt es einen richtigen Weg, um das gewünschte Ergebnis zu erzielen? Die einzige Problemumgehung, die ich bisher gefunden habe, ist die Implementierung des Codes mit Assembler.

Vollständiger Testcode und zusätzliche Details
Der Vollständigkeit halber ist hier ein vollständiger Testcode, der beide Probleme zeigt (keine Speicherbarrieren und das Optimierungsproblem). Es erzeugt die folgende Ausgabe auf drei verschiedenen Windows-Rechnern für dmd 2.049 und dmd 2.050 (unter der Annahme, dass Dekkers Algorithmus nicht blockiert, was passieren könnte):

%Vor%

Und die Schleife innerhalb von atomicInc wird dazu mit voller Optimierung kompiliert:

%Vor%

Hier ist der Testcode:

%Vor%     
stephan 12.11.2010, 13:35
quelle

2 Antworten

4

Obwohl die Probleme immer noch zu bestehen scheinen, macht core.atomic jetzt atomicLoad verfügbar, was eine relativ einfache Problemumgehung ermöglicht. Damit das cas -Beispiel funktioniert, reicht es, cnt atomisch zu laden:

%Vor%

Ähnlich, um Dekkers Algorithmus zu verwenden:

%Vor%

Für andere Architekturen als ia32 (Stringoperationen und SSE werden ignoriert), die auch neu ordnen können

  • liest relativ zu reads
  • oder schreibt relativ zu Schreibvorgängen
  • oder schreibt und liest an denselben Speicherort

zusätzliche Speicherbarrieren wären erforderlich.

    
stephan 16.03.2012, 10:07
quelle
3

Ja, codiere es in Assembler. Wenn Sie die Funktion cas () überspringen und nur Ihre gesamte atomicInt-Funktion in die Assembly schreiben, sind es nur ein paar Codezeilen. Bis Sie dies tun, werden Sie wahrscheinlich gegen die Optimierungen des Compilers kämpfen.

Darüber hinaus können Sie die x86 LOCK INC-Anweisung anstelle von CAS verwenden, und Sie sollten in der Lage sein, die Funktion auf nur eine oder zwei Zeilen der Assembly zu reduzieren.

    
Timothy Baldridge 12.11.2010 13:48
quelle

Tags und Links