Linux-Synchronisations-Primitive (Spinlock, Mutex, RCUs) verwenden Speicherbarrierenanweisungen, um zu erzwingen, dass die Speicherzugriffsanweisungen neu angeordnet werden. Und diese Neuordnung kann entweder von der CPU selbst oder vom Compiler vorgenommen werden.
Kann jemand einige Beispiele für GCC-Code zeigen, wo solche Nachbestellungen gemacht werden? Ich bin hauptsächlich an x86 interessiert. Der Grund, warum ich das frage, ist zu verstehen, wie GCC entscheidet, welche Anweisungen nachbestellt werden können. Verschiedene x86-Mikroarchitekturen (z. B. Sandy Bridge vs Efeu Bridge) verwenden unterschiedliche Cache-Architekturen. Daher frage ich mich, wie GCC eine effektive Neuordnung durchführt, die unabhängig von der Cache-Architektur die Ausführungsleistung unterstützt. Ein bestimmter beispielhafter C-Code und neu geordneter GCC-generierter Code wäre sehr nützlich. Danke!
Die Neuanordnung, die GCC machen kann, hängt nicht mit der Neuordnung einer (x86) CPU zusammen.
Beginnen wir mit Compiler-Neuordnung . Die Regeln der C-Sprache sind so, dass es GCC verboten ist, volatile
loads neu anzuordnen und Speicherzugriffe gegeneinander zu speichern oder zu löschen, wenn ein Sequenzpunkt zwischen ihnen auftritt (Dank bobc für diese Klarstellung). Das heißt, in der Assembly-Ausgabe erscheinen diese Speicherzugriffe und werden genau in der von Ihnen angegebenen Reihenfolge sequenziert. Nicht- volatile
-Zugriffe können andererseits in Bezug auf alle anderen Zugriffe, volatile
oder nicht, neu geordnet werden, vorausgesetzt, dass (durch die Als-ob-Regel) das Endergebnis der Berechnung dasselbe ist. p>
Zum Beispiel könnte eine nicht volatile
-Last im C-Code so oft ausgeführt werden, wie der Code sagt, aber in einer anderen Reihenfolge (zB wenn der Compiler es für bequemer hält, es früher oder später zu tun, wenn mehr Register sind verfügbar). Es könnte weniger oft gemacht werden, als der Code sagt (z.B. wenn eine Kopie des Wertes noch in einem Register in der Mitte eines großen Ausdrucks verfügbar ist). Oder es könnte sogar gelöscht werden (z. B. wenn der Compiler die Nutzlosigkeit der Ladung nachweisen kann oder wenn er eine Variable vollständig in ein Register verschoben hat).
Um zu anderen Zeiten Neuordnungen des Compilers zu verhindern, müssen Sie eine Compiler-spezifische Barriere verwenden. GCC verwendet __asm__ __volatile__("":::"memory");
für diesen Zweck.
Dies unterscheidet sich von der CPU-Neuordnung , also dem Speicherordnungsmodell . Alte CPUs haben Befehle genau in der Reihenfolge ausgeführt, in der sie im Programm erschienen. Dies wird Programmreihenfolge oder das starke Speicherordnungsmodell genannt. Moderne CPUs greifen jedoch manchmal auf "Cheats" zurück, um schneller zu laufen, indem sie das Speichermodell ein wenig schwächen.
Die Art und Weise, wie x86-CPUs das Speichermodell schwächen, ist in Intels Software Developer Manuals, Band 3, Kapitel 8, Abschnitt 8.2.2 "Speicherreihenfolge in P6 und neuere Prozessorfamilien" dokumentiert. Dies ist teilweise, was es liest:
Es gibt auch sehr gute Beispiele dafür, was man neu ordnen kann und was nicht, in Abschnitt 8.2.3 "Beispiele zur Veranschaulichung der Prinzipien der Speicherordnung" .
Wie Sie sehen können, verwendet man FENCE-Anweisungen, um zu verhindern, dass eine x86-CPU Speicherzugriffe fälschlicherweise neu anordnet.
Zu guter Letzt könnten Sie an diesem Link interessiert sein, der weiter ins Detail geht und kommt mit den Assembly-Beispielen, nach denen Sie sich sehnen.
Das ist nicht wahr und ist ziemlich irreführend. Die C-Spezifikation gibt keine solche Garantie. Siehe Wann wird auf ein flüchtiges Objekt zugegriffen?
Der Standard ermutigt Compiler, Optimierungen in Bezug auf Zugriffe auf flüchtige Objekte zu unterlassen, lässt aber die Implementierung als einen flüchtigen Zugriff definiert. Die Mindestanforderung ist, dass sich an einem Sequenzpunkt alle vorherigen Zugriffe auf flüchtige Objekte stabilisiert haben und keine nachfolgenden Zugriffe aufgetreten sind. Somit ist es einer Implementierung freigestellt, flüchtige Zugriffe zwischen Sequenzpunkten neu zu ordnen und zu kombinieren, dies jedoch nicht für Zugriffe über einen Sequenzpunkt hinweg.
Traditionell haben Programmierer sich auf volatile als billige Synchronisationsmethode verlassen, aber das ist keine zuverlässige Methode mehr.
Tags und Links memory gcc compiler-optimization linux-kernel cpu