1 Punkte von GN⁺ 4 시간 전 | 1 Kommentare | Auf WhatsApp teilen
  • 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

 
GN⁺ 4 시간 전
Lobste.rs-Kommentare
  • Mir gefiel der Kommentar, der Raymond Chen Loop Unrolling erklärte

    • Der Kommentar könnte nicht nur für den Blogautor, sondern auch für allgemeine Leser geschrieben worden sein
      Nicht jeder, der solche Texte liest, kennt den gesamten Hintergrund, und viele sind dankbar für Hinweise, mit denen sie mehr dazulernen können
    • Den Kommentar habe ich nicht gesehen; er wurde wohl gelöscht. Aber wenn es um Raymond Chen geht: der Mann ist eine lebende Legende
      https://joelonsoftware.com/2004/06/…
    • Das erinnert mich daran, wie auf Slashdot vor Jahrzehnten jemand versuchte, larry@wall.org das Thema Perl zu erklären
  • Ich vermute, das war auf Alpha. In den x86-Emulator für diese Plattform war enorm viel Arbeit geflossen