Der Unterschied zwischen asm, asm volatile und clobbering memory

74

Bei der Implementierung sperrenfreier Datenstrukturen und Timing-Codes müssen häufig die Optimierungen des Compilers unterdrückt werden. Normalerweise wird dies asm volatilemit memoryin der Clobber-Liste verwendet, aber manchmal wird nur asm volatileoder nur ein einfacher asmClobber-Speicher angezeigt .

Welche Auswirkungen haben diese unterschiedlichen Anweisungen auf die Codegenerierung (insbesondere in GCC, da es unwahrscheinlich ist, dass sie portabel sind)?

Nur als Referenz sind dies die interessanten Variationen:

asm ("");   // presumably this has no effect on code generation
asm volatile ("");
asm ("" ::: "memory");
asm volatile ("" ::: "memory");
jleahy
quelle
1
Jemand scheint viel zu nah am Metall herumzuspielen :-) (Und woanders tippt @Mysticial eine lächerlich detaillierte Antwort ab ...)
Kerrek SB

Antworten:

65

Siehe die Seite "Erweiterter Asm" in der GCC-Dokumentation .

Sie können verhindern, dass eine asmAnweisung gelöscht wird, indem Sie das Schlüsselwort volatilenach dem schreiben asm. [...] Das volatileSchlüsselwort zeigt an, dass die Anweisung wichtige Nebenwirkungen hat. GCC löscht einen volatileASM nicht, wenn er erreichbar ist.

und

Ein asmBefehl ohne Ausgangsoperanden wird identisch mit einem flüchtigen asmBefehl behandelt.

In keinem Ihrer Beispiele sind Ausgabeoperanden angegeben, daher verhalten sich die Formulare asmund asm volatileidentisch: Sie erstellen einen Punkt im Code, der möglicherweise nicht gelöscht wird (es sei denn, er ist nicht erreichbar).

Das ist nicht ganz dasselbe wie nichts zu tun. In dieser Frage finden Sie ein Beispiel für einen Dummy, der asmdie Codegenerierung ändert. In diesem Beispiel wird Code, der 1000 Mal eine Schleife umrundet, in Code vektorisiert, der 16 Iterationen der Schleife gleichzeitig berechnet. Das Vorhandensein eines asminnerhalb der Schleife verhindert jedoch die Optimierung (die asmmuss 1000-mal erreicht werden).

Der "memory"Clobber lässt GCC davon ausgehen, dass ein beliebiger Speicher vom asmBlock willkürlich gelesen oder geschrieben werden kann , sodass der Compiler keine Lasten oder Speicher darüber neu anordnen kann:

Dies führt dazu, dass GCC Speicherwerte nicht in Registern über den Assembler-Befehl zwischengespeichert hält und Speicher oder Ladevorgänge in diesem Speicher nicht optimiert.

(Dies hindert eine CPU jedoch nicht daran, Lasten und Speicher in Bezug auf eine andere CPU neu zu ordnen. Dafür benötigen Sie echte Anweisungen zur Speicherbarriere.)

Matthew Slattery
quelle
Das ist eigentlich sehr interessant, nicht zu realisieren, dass gcc asmBlöcke ohne Ausgänge als flüchtig behandelt, war eine große Lücke in meinem Wissen.
Jleahy
Also volatile= Performance-Killer, egal in welchem ​​Kontext es verwendet wird (variabel oder asm). Datei mit dem gotoSchlüsselwort - nur verwenden, wenn dies unbedingt erforderlich ist.
Etherice
"irgendein Speicher" bedeutet irgendein Objekt im Speicher?
neugieriger Kerl
3
Ein "memory"Clobber gilt nur für global erreichbaren Speicher oder Speicher, der über Zeigereingaben auf die asmAnweisung erreichbar ist. Was die C-Objekte betrifft, die im Speicher "synchron" sein müssen und die sich noch in Registern befinden können, ist dies wie ein Nicht-Inline-Funktionsaufruf. Daher können lokale Variablen, deren Adresse noch nie außerhalb der Funktion übergeben wurde (z. B. Schleifenzähler), dank der Escape-Analyse normalerweise immer noch in Registern verbleiben .
Peter Cordes
1
Diese Optimierung ist sicher, da es bereits nicht sicher ist / erlaubt ist, asm("incl -16(%%rbp)" ::: "memory")auf den Stapelbereich zuzugreifen, in dem gcc zufällig einen lokalen Speicherplatz einfügt (ohne einen "+m"Operanden zu verwenden, damit der Compiler einen Adressierungsmodus generiert). Über das Stack-Frame-Layout können Sie keine Annahmen treffen. Verschiedene Compiler-Optionen ändern dies. Auf jeden "memory"Fall macht ein Clobber das, was diese Antwort sagt, aber mit einer Leistungsstrafe, die nicht ganz so schlimm ist.
Peter Cordes
12

asm ("") tut nichts (oder zumindest soll es nichts tun.

asm volatile ("") macht auch nichts.

asm ("" ::: "memory") ist ein einfacher Compilerzaun.

asm volatile ("" ::: "memory")AFAIK ist das gleiche wie das vorherige. Das volatileSchlüsselwort teilt dem Compiler mit, dass dieser Assemblyblock nicht verschoben werden darf. Beispielsweise kann es aus einer Schleife gehoben werden, wenn der Compiler entscheidet, dass die Eingabewerte bei jedem Aufruf gleich sind. Ich bin mir nicht sicher, unter welchen Bedingungen der Compiler entscheiden wird, dass er genug über die Assembly versteht, um zu versuchen, ihre Platzierung zu optimieren, aber das volatileSchlüsselwort unterdrückt dies vollständig. Trotzdem wäre ich sehr überrascht, wenn der Compiler versuchen würde, eine asmAnweisung zu verschieben, die keine deklarierten Ein- oder Ausgänge hat.

volatileVerhindert übrigens auch, dass der Compiler den Ausdruck löscht, wenn er entscheidet, dass die Ausgabewerte nicht verwendet werden. Dies kann jedoch nur passieren, wenn Ausgabewerte vorhanden sind, sodass dies nicht gilt asm ("" ::: "memory").

Lily Ballard
quelle
11
Die Antwort von Matthew Slattery weist darauf hin, dass dies asm volatile ("")nicht ganz das Gleiche ist wie nichts zu tun, da dies drastische Auswirkungen auf die Compileroptimierung haben kann. Die gleichen Auswirkungen auf die Leistung würden für die Verwendung asm volatile ("" ::: "memory")als Compiler-Zaun gelten.
Etherice
Der Compiler versteht die Assemblersprache nicht!
Neugieriger
2
@curiousguy nein, aber es versteht, wann ein asmBlock Ein- / Ausgänge deklariert hat, was dem Compiler mitteilt, von welchen Registern er abhängt und welche er ändern wird, und daher kann der Compiler bestimmte Berechnungen mischen, wenn sie die Eingänge nicht beeinflussen / Ausgänge.
Lily Ballard
Zumindest für GCC verhindert asm volatilees nicht die allgemeine Neuordnung von Befehlen, sondern verhindert nur, dass ein erreichbarer asmBlock aufgrund (offensichtlicher) fehlender bedeutsamer Nebenwirkungen gelöscht wird (und verhindert bei neueren GCC, dass er aus einer Schleife herausgezogen wird, selbst wenn Der Compiler stellt fest, dass die Eingaben immer gleich sind. Andernfalls wird die Neuordnung von Befehlen nur durch deklarierte Ein- und Ausgänge (und Pseudoausgänge wie "memory") verhindert. Lesen Sie mehr in den Dokumenten .
ShadowRanger
3

Nur der Vollständigkeit halber auf Lily Ballard Antwort , Visual Studio 2010 bietet _ReadBarrier(), _WriteBarrier()und _ReadWriteBarrier()das Gleiche zu tun (VS2010 nicht zulässt , dass Inline - Assembler für 64-Bit - Anwendungen).

Diese generieren keine Anweisungen, wirken sich jedoch auf das Verhalten des Compilers aus. Ein schönes Beispiel ist hier .

MemoryBarrier() erzeugt lock or DWORD PTR [rsp], 0

James
quelle