Bei der Implementierung sperrenfreier Datenstrukturen und Timing-Codes müssen häufig die Optimierungen des Compilers unterdrückt werden. Normalerweise wird dies asm volatile
mit memory
in der Clobber-Liste verwendet, aber manchmal wird nur asm volatile
oder nur ein einfacher asm
Clobber-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");
c
gcc
inline-assembly
jleahy
quelle
quelle
Antworten:
Siehe die Seite "Erweiterter Asm" in der GCC-Dokumentation .
und
In keinem Ihrer Beispiele sind Ausgabeoperanden angegeben, daher verhalten sich die Formulare
asm
undasm volatile
identisch: 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
asm
die 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 einesasm
innerhalb der Schleife verhindert jedoch die Optimierung (dieasm
muss 1000-mal erreicht werden).Der
"memory"
Clobber lässt GCC davon ausgehen, dass ein beliebiger Speicher vomasm
Block willkürlich gelesen oder geschrieben werden kann , sodass der Compiler keine Lasten oder Speicher darüber neu anordnen kann:(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.)
quelle
asm
Blöcke ohne Ausgänge als flüchtig behandelt, war eine große Lücke in meinem Wissen.volatile
= Performance-Killer, egal in welchem Kontext es verwendet wird (variabel oder asm). Datei mit demgoto
Schlüsselwort - nur verwenden, wenn dies unbedingt erforderlich ist."memory"
Clobber gilt nur für global erreichbaren Speicher oder Speicher, der über Zeigereingaben auf dieasm
Anweisung 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 .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.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. Dasvolatile
Schlü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 dasvolatile
Schlüsselwort unterdrückt dies vollständig. Trotzdem wäre ich sehr überrascht, wenn der Compiler versuchen würde, eineasm
Anweisung zu verschieben, die keine deklarierten Ein- oder Ausgänge hat.volatile
Verhindert ü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 giltasm ("" ::: "memory")
.quelle
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 Verwendungasm volatile ("" ::: "memory")
als Compiler-Zaun gelten.asm
Block 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.asm volatile
es nicht die allgemeine Neuordnung von Befehlen, sondern verhindert nur, dass ein erreichbarerasm
Block 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 .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()
erzeugtlock or DWORD PTR [rsp], 0
quelle