GCCs Neuanordnung von Lese- / Schreibbefehlen

8

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!

    
Manohar 28.02.2014, 22:13
quelle

2 Antworten

15

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:

  • Lesevorgänge werden nicht mit anderen Lesevorgängen neu geordnet.
  • Schreibvorgänge werden nicht mit älteren Lesevorgängen neu geordnet.
  • Schreibvorgänge in den Speicher werden nicht mit anderen Schreibvorgängen mit [einigen] Ausnahmen neu geordnet.
  • Lesevorgänge können mit älteren Schreibvorgängen an verschiedenen Speicherorten neu angeordnet werden, jedoch nicht mit älteren Schreibvorgängen am selben Speicherort.
  • Lese- oder Schreibvorgänge können nicht mit E / A-Anweisungen, gesperrten Anweisungen oder Serialisierungsanweisungen neu geordnet werden.
  • Lesevorgänge können frühere LFENCE- und MFENCE-Anweisungen nicht bestehen.
  • Writes können frühere LFENCE-, SFENCE- und MFENCE-Anweisungen nicht weitergeben.
  • LFENCE-Befehle können frühere Lesevorgänge nicht bestehen.
  • SFENCE-Anweisungen können keine früheren Schreibvorgänge passieren.
  • MFENCE-Befehle können keine früheren Lese- oder Schreibvorgänge bestehen.

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.

    
Iwillnotexist Idonotexist 03.03.2014, 09:07
quelle
2
  • Die Regeln der C-Sprache sind so beschaffen, dass es GCC verboten ist, flüchtige Lasten neu zu ordnen und Speicherzugriffe in Bezug zueinander zu speichern oder zu löschen.

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.

    
bobc 12.09.2015 08:12
quelle