Wie das x86-Emulator-Team so schlechten Code fand, dass es ihn während der Emulation einfach reparierte
(devblogs.microsoft.com)- Der x86-32-Emulator erzeugte per Binärübersetzung nativen Code, um x86-32-Code auf anderen Prozessoren auszuführen, und bot damit eine deutlich bessere Leistung als ein Interpreter-Ansatz
- Der betreffende Emulator lässt sich so verstehen, dass er x86-32 wie Bytecode behandelt und der Emulator ähnlich wie ein JIT-Compiler arbeitet
- Ein Programm musste etwa 64 KB Speicher auf dem Stack reservieren und initialisieren; üblich wäre dabei, nach einem Stack-Probe den Stack-Pointer zu verringern und den Speicher in einer kleinen Schleife zu initialisieren
- Der Compiler dieses Codes erzeugte statt einer Schleife 65.536 einzelne Byte-Schreibbefehle, und da jeder Befehl 4 Byte groß war, wurden zum Initialisieren von 64 KB Daten 256 KB Code benötigt
- Das Emulator-Team ergänzte den Übersetzer um Spezialcode, der diese Funktion erkennt und sie durch eine äquivalente kurze Schleife ersetzt
Hintergrund: x86-32-Emulator und Binärübersetzung
- Windows enthielt früher einen x86-32-Prozessor-Emulator für Systeme, die auf anderen Prozessoren als x86-32 liefen
- Auf welchen Prozessor sich dieser Fall genau bezog, wird im Original nicht genannt
- Der Emulator nutzte Binärübersetzung, um nativen Code zu erzeugen, der sich äquivalent zum ursprünglichen x86-32-Code verhielt
- Dieser Ansatz brachte gegenüber interpreterbasierter Emulation einen erheblichen Leistungsvorteil
- Man kann x86-32 dabei als Bytecode und den Emulator als eine Art JIT-Compiler verstehen
Problematischer Code: Initialisierung von 64 KB Stack-Speicher
- Ein Programm musste etwa 64 KB Speicher auf dem Stack reservieren und initialisieren
- Das Standardverfahren war zunächst ein Stack-Probe, um zu prüfen, ob 64 KB Speicher verwendet werden konnten
- Danach wurde der Stack-Pointer um 65.536 verringert, und der Speicher wurde üblicherweise in einer kleinen, engen Schleife initialisiert
Exzessives Loop-Unrolling durch den Compiler
- Der Compiler, der diesen Code übersetzte, erzeugte keine Schleife zur Initialisierung jedes einzelnen Bytes
- Stattdessen rollte er die Schleife vollständig aus und erzeugte 65.536 einzelne „Byte in den Speicher schreiben“-Befehle
- Jeder Befehl war 4 Byte lang
- Dadurch waren zum Initialisieren von 64 KB Daten insgesamt 256 KB Code nötig
Reaktion des Emulator-Teams
- Das Emulator-Team ergänzte den Übersetzer um Spezialcode, der diese Funktion erkennt
- Die erkannte Funktion wurde durch eine äquivalente kurze Schleife ersetzt
- Anstatt den ursprünglichen Programmcode unverändert zu übersetzen, wandelte diese Behandlung ineffiziente Code-Muster während der Emulation in eine kompaktere Form um
1 Kommentare
Lobste.rs-Kommentare
Mir gefiel der Kommentar, der Raymond Chen Loop Unrolling erklärte
Nicht jeder, der solche Texte liest, kennt den gesamten Hintergrund, und viele sind dankbar für Hinweise, mit denen sie mehr dazulernen können
https://joelonsoftware.com/2004/06/…
Ich vermute, das war auf Alpha. In den x86-Emulator für diese Plattform war enorm viel Arbeit geflossen