Emulationsübungen: Xbox 360 FMA-Handbuch


Vor vielen Jahren habe ich in der Microsoft Xbox 360-Abteilung gearbeitet. Wir haben über die Veröffentlichung einer neuen Konsole nachgedacht und beschlossen, dass es großartig wäre, wenn diese Konsole Spiele von der Konsole der vorherigen Generation ausführen könnte.

Die Emulation ist immer schwierig, aber es stellt sich als noch schwieriger heraus, wenn Ihre Unternehmenschefs ständig die Arten von Zentralprozessoren ändern. Die erste Xbox (nicht zu verwechseln mit der Xbox One) verwendete eine x86-CPU. In der zweiten Xbox verwendete die Xbox 360 leider einen PowerPC-Prozessor. Die dritte Xbox, d. H. Die Xbox One , verwendete die x86 / x64-CPU. Solche Sprünge zwischen verschiedenen ISAs haben unser Leben nicht vereinfacht.

Ich nahm an der Arbeit eines Teams teil, das der Xbox 360 beigebracht hat, viele Spiele der ersten Xbox zu emulieren, dh x86 auf PowerPC zu emulieren, und für diese Arbeit erhielt ich den Titel „Ninja-Emulation“ . Dann wurde ich gebeten, das Problem der Emulation der Xbox 360 PowerPC-CPU auf einer x64-CPU zu untersuchen. Ich werde im Voraus sagen, dass ich keine zufriedenstellende Lösung gefunden habe.


FMA! = MMA


Eines der Dinge, die mich störten, war Fused Multiplly Add oder FMA- Anweisungen. Diese Anweisungen erhielten drei Parameter am Eingang, multiplizierten die ersten beiden und addierten dann den dritten. Verschmolzen bedeutete, dass das Runden erst am Ende der Operation durchgeführt wurde. Das heißt, die Multiplikation wird mit voller Genauigkeit durchgeführt, wonach die Addition durchgeführt wird, und erst dann wird das Ergebnis auf die endgültige Antwort gerundet.

Um dies anhand eines konkreten Beispiels zu zeigen, stellen wir uns vor, wir verwenden dezimale Gleitkommazahlen und zwei Präzisionsziffern. Stellen Sie sich diese Berechnung als Funktion vor:

FMA(8.1e1, 2.9e1, 4.1e1), 8.1e1 * 2.9e1 + 4.1e1, 81 * 29 + 41

81*29 ist gleich 2349 und nach dem Hinzufügen von 41 erhalten wir 2390 . Auf zwei Ziffern 2.4e3 wir 2400 oder 2.4e3 .

Wenn wir keine FMA haben, müssen wir zuerst die Multiplikation durchführen, 2349 , was auf zwei Stellen Genauigkeit 2300 (2.3e3) und 2300 (2.3e3) . Dann addieren wir 41 und erhalten 2341 , das erneut gerundet wird, und wir erhalten das Endergebnis 2300 (2.3e3) , das weniger genau ist als die FMA-Antwort.

Anmerkung 1: FMA(a,b, -a*b) berechnet den Fehler in a*b , der tatsächlich cool ist.

Anmerkung 2: Eine der Nebenwirkungen von Anmerkung 1 ist, dass x = a * b – a * b möglicherweise nicht Null zurückgibt, wenn der Computer automatisch FMA-Anweisungen generiert.

Daher liefert die FMA offensichtlich genauere Ergebnisse als einzelne Multiplikations- und Additionsanweisungen. Wir werden nicht tief gehen, aber wir werden uns einig sein, dass die FMA genauer ist als ihre Alternativen, wenn wir zwei Zahlen multiplizieren und dann die dritte addieren müssen. Außerdem haben FMA-Befehle häufig eine geringere Latenz als der Multiplikationsbefehl, dem der Additionsbefehl folgt. In der Xbox 360-CPU waren die Latenz und die FMA-Verarbeitungsgeschwindigkeit gleich denen von fmul oder fadd , sodass die Verwendung von FMA anstelle von fmul gefolgt von abhängigem fadd die Verzögerung um die Hälfte reduzieren konnte.

FMA-Emulation


Der Xbox 360-Compiler hat immer FMA-Anweisungen generiert, sowohl Vektor- als auch Skalaranweisungen. Wir waren uns nicht sicher, ob die von uns ausgewählten x64-Prozessoren diese Anweisungen unterstützen würden. Daher war es wichtig, sie schnell und genau zu emulieren. Es war notwendig, dass unsere Emulation dieser Anweisungen ideal wurde, da ich aus meiner früheren Erfahrung mit der Emulation von Gleitkommaberechnungen wusste, dass „ziemlich nahe“ Ergebnisse dazu führten, dass Charaktere durch den Boden fielen, Autos aus der Welt flogen und so weiter.

Was wird also benötigt , um FMA-Anweisungen perfekt zu emulieren, wenn die x64-CPU sie nicht unterstützt?

Glücklicherweise wird die überwiegende Mehrheit der Gleitkommaberechnungen in Spielen mit Gleitkomma-Genauigkeit (32 Bit) ausgeführt, und ich könnte gerne Anweisungen mit doppelter Genauigkeit (64 Bit) in der FMA-Emulation verwenden.

Es scheint, dass das Emulieren von FMA-Anweisungen mit Float-Genauigkeit unter Verwendung von Berechnungen mit doppelter Genauigkeit einfach sein sollte ( Sprecherstimme: aber nicht; Gleitkommaoperationen sind niemals einfach ). Float hat eine Genauigkeit von 24 Bit und Double eine Genauigkeit von 53 Bit. Dies bedeutet, dass Sie die Multiplikation fehlerfrei durchführen können, wenn Sie den eingehenden Float in Precision Double (verlustfreie Konvertierung) konvertieren. Das heißt, um vollständig genaue Ergebnisse zu speichern, reichen nur 48 Bit Genauigkeit aus, und wir haben mehr, das heißt, alles ist in Ordnung.

Dann müssen wir den Zusatz machen. Es reicht aus, nur den zweiten Term im Float-Format zu nehmen, ihn in Double zu konvertieren und ihn dann zum Ergebnis der Multiplikation hinzuzufügen. Da beim Multiplizieren keine Rundung auftritt und erst nach der Addition durchgeführt wird, reicht dies vollständig aus, um die FMA zu emulieren. Unsere Logik ist perfekt. Sie können den Sieg erklären und nach Hause zurückkehren.

Der Sieg war so nah ...


Das geht aber nicht. Oder zumindest schlägt es für einige der eingehenden Daten fehl. Überlegen Sie, warum dies passieren kann.

Call Hold Musik klingt ...

Ein Fehler tritt auf, weil nach der Definition von FMA Multiplikation und Addition mit voller Genauigkeit durchgeführt werden, wonach das Ergebnis mit einem Präzisions-Float gerundet wird. Das haben wir fast geschafft.

Die Multiplikation erfolgt ohne Rundung, und nach der Addition wird eine Rundung durchgeführt. Dies ähnelt dem , was wir versuchen zu tun. Das Runden nach dem Hinzufügen erfolgt jedoch mit doppelter Präzision. Danach müssen wir das Ergebnis mit Float-Präzision speichern, weshalb erneut gerundet wird.

Pooh Doppelte Rundung .

Es wird schwierig sein, dies klar zu demonstrieren. Kehren wir also zu unseren Dezimal-Gleitkommaformaten zurück, bei denen die einfache Genauigkeit zwei Dezimalstellen und die doppelte Genauigkeit vier Stellen beträgt. Stellen wir uns vor, wir berechnen FMA(8.1e1, 2.9e1, 9.9e-1) oder 81 * 29 + .99 .

Die genaue Antwort auf diesen Ausdruck wäre 2349.99 oder 2.34999e3 . 2.3e3 wir auf Präzision einfach (zwei Ziffern) 2.3e3 , erhalten wir 2.3e3 . Mal sehen, was schief geht, wenn wir versuchen, diese Berechnungen zu emulieren.

Wenn wir 81 und 29 mit einer Genauigkeit von doppelt multiplizieren, erhalten wir 2349 . So weit so gut.

Dann addieren wir .99 und erhalten 2349.99 . Alles ist noch gut.

Dieses Ergebnis wird auf die Genauigkeit von double gerundet und wir erhalten 2350 (2.350e3) . Ups

Wir runden es auf die Präzisions-Single und erhalten gemäß den IEEE- Rundungsregeln auf 2400 (2.4e3) . Das ist die falsche Antwort. Es hat einen etwas größeren Fehler als das korrekt gerundete Ergebnis, das von der FMA-Anweisung zurückgegeben wird.

Sie können angeben, dass das Problem in der IEEE-Umgebungsregel liegt, bis die nächste gerade ist. Unabhängig davon, für welche Rundungsregel Sie sich entscheiden, wird es immer einen Fall geben, in dem die doppelte Rundung ein Ergebnis liefert, das sich von der tatsächlichen FMA unterscheidet.

Wie ist das alles ausgegangen?


Ich konnte keine vollständig zufriedenstellende Lösung für dieses Problem finden.

Ich habe das Xbox-Team lange vor der Veröffentlichung der Xbox One verlassen und seitdem habe ich der Konsole nicht viel Aufmerksamkeit geschenkt, sodass ich nicht weiß, welche Entscheidung sie getroffen haben. Moderne x64-CPUs verfügen über FMA-Anweisungen, die solche Operationen perfekt emulieren können. Sie können den mathematischen x87-Coprozessor auch irgendwie verwenden, um FMA zu emulieren. Ich kann mich nicht erinnern, zu welcher Schlussfolgerung ich gekommen bin, als ich diese Frage untersucht habe. Oder vielleicht haben die Entwickler einfach entschieden, dass die Ergebnisse ziemlich nahe beieinander liegen und verwendet werden können.

Source: https://habr.com/ru/post/de447680/


All Articles