1 Punkte von GN⁺ 2024-08-28 | 1 Kommentare | Auf WhatsApp teilen
  • Der RV64 DynaRec von Box64 hat sich innerhalb eines Jahres von einfachen nativen Linux-Spielen bis dahin entwickelt, The Witcher 3 auf einem RISC-V-PC auszuführen
  • Auslöser des Fortschritts war, dass die Nutzung von AMD-Grafikkarten möglich wurde; dadurch fielen OpenGL-Einschränkungen weg, und mehr x86-Programme konnten getestet sowie Bugs behoben werden
  • Das RV64-Backend implementiert weniger x86-Instruktionen als das ARM64-Backend, und auch bei AVX-Instruktionen gibt es auf RISC-V noch große offene Aufgaben
  • RISC-V fehlen Bitfeld-Extraktion/-Einfügung und atomare 16-Byte-Instruktionen, wodurch die Übersetzungskosten bei der x86-Emulation höher sind als bei AArch64 oder LoongArch64
  • The Witcher 3 läuft tatsächlich mit box64; im Spiel werden bis zu 15 fps erreicht, im Hauptmenü läuft es mit voller Geschwindigkeit

Bis The Witcher 3 auf RISC-V lief

  • Vor einem Jahr konnte RV64 DynaRec nur relativ leicht auszuführende native Linux-Spiele wie Stardew Valley und World of Goo starten
  • Damals gab es im Wesentlichen zwei Flaschenhälse
    • Beim schnellen Hinzufügen von x86_64-Instruktionen zum RISC-V-Backend waren noch viele DynaRec-Bugs vorhanden
    • Die in VisionFive 2 und LicheePi 4A integrierten IMG-GPUs unterstützten nur OpenGL ES, aber kein OpenGL
  • Mit gl4es ließ sich teilweise OpenGL-Unterstützung erreichen, sodass Spiele wie Stardew Valley liefen, doch für schwergewichtigere Linux-Spiele und typische Windows-Spiele reichte das nicht aus
  • Sophgos Milk-V Pioneer ist ein RISC-V-PC mit 64 Kernen und bietet einen PCIe-Steckplatz, in den eine Grafikkarte eingesetzt werden kann
  • Ein weiterer Mitwirkender, xctan, fand eine Möglichkeit, eine AMD-Grafikkarte über die M.2-Schnittstelle an das VisionFive 2 „anzuschließen“
  • Als AMD-Grafikkarten nutzbar wurden, erweiterte sich der Kreis der testbaren x86-Programme; daraufhin wurden zahlreiche Bugs in RV64 DynaRec behoben und viele x86-Instruktionen ergänzt
  • Infolgedessen funktionierte The Witcher 3 direkt beim ersten Start

Aktueller Stand von RV64 DynaRec

  • Der x86-Instruktionssatz ist sehr groß, und auch der Implementierungsumfang unterscheidet sich je nach Backend
    • Das ARM64-Backend implementiert insgesamt über 1.600 x86-Instruktionen
    • Das RV64-Backend implementiert rund 1.000 x86-Instruktionen
    • Mehr als 300 davon sind neu unterstützte AVX-Instruktionen, die auf RISC-V noch überhaupt nicht implementiert sind
  • Auch bei der Implementierung von SSE-Instruktionen ist RISC-V leistungsmäßig im Nachteil
    • Das RV64-Backend implementiert SSE-Instruktionen als skalare Instruktionen
    • AArch64 nutzt die Neon-Erweiterung, LoongArch64 die LSX-Erweiterung
    • Wegen dieses Unterschieds ist die Performance deutlich niedriger als bei den beiden anderen Backends
  • RISC-V verfügt mit RVV über eine Vektorerweiterung
    • Milk-V Pioneer unterstützt die Erweiterung xtheadvector, eine Variante von RVV 0.7.1
    • Das SpacemiT K1/M1-SoC unterstützt das ratifizierte RVV 1.0
    • Banana Pi F3 und Milk-V Jupiter mit diesem SoC sind erhältlich
  • In box64 wurden kürzlich grundlegende RVV-Unterstützung und Implementierungen einiger verbreiteter SSE-Instruktionen ergänzt
  • Die Arbeit an RVV befindet sich noch in einem sehr frühen Stadium und hilft derzeit nicht bei der Performance-Verbesserung

RISC-V-Instruktionen, die für x86-Emulation besonders fehlen

  • Aus Sicht der x86-Emulation ist RISC-V unter den drei unterstützten Architekturen eher die ausdrucksschwächste
  • Im Vergleich zu AArch64 und LoongArch64 fehlen praktische Instruktionen, sodass für die Emulation derselben Operation mehr Instruktionen nötig sind
  • Insbesondere fehlen zwei wichtige Funktionen
    • Die Möglichkeit, einen bestimmten Bitbereich aus einem Register auszuwählen und in ein anderes Register zu übernehmen
    • Die Möglichkeit, einige Bits eines Registers in einen bestimmten Bereich eines anderen Registers einzufügen
  • LoongArch64 und AArch64 besitzen entsprechende Instruktionen
    • LoongArch64 verwendet BSTRPICK.D und BSTRINS.D
    • ARM64 nutzt die Opcodes UBFX und BFI
  • In RISC-V gibt es weder in offiziellen Erweiterungen noch in Vendor-Erweiterungen entsprechende Instruktionen

Was das Beispiel ADD AH, BL über Übersetzungskosten zeigt

  • Die x86-ISA neigt dazu, unveränderte Bits beizubehalten, daher sind Operationen auf Teilregistern wichtig
  • Bei ADD AH, BL muss box64 Folgendes tun
    • Das niederwertigste Byte von RBX extrahieren
    • Es zum zweitniederwertigsten Byte von RAX addieren
    • Das Ergebnis wieder in das zweitniederwertigste Byte von RAX einfügen
    • Die übrigen Bytes von RAX unverändert lassen
  • Auf LoongArch64 lässt sich das mit BSTRPICK.D, ADD und BSTRINS.D einfach und intuitiv implementieren
  • Auf RISC-V müssen für dieselbe Operation Shifts, Masken, AND, OR und Ähnliches kombiniert werden, sodass 10 Instruktionen nötig sind
  • Solche Fälle sind keine isolierten Beispiele; x86 enthält viele Instruktionen ähnlicher Art, was die RISC-V-Implementierung aufwendiger macht

Einschränkungen bei atomaren 16-Byte-Instruktionen

  • x86 verfügt über Instruktionen mit LOCK-Präfix für lock-free atomare Operationen
  • box64 emuliert sie hauptsächlich mit LR/SC-Sequenzen
    • LR/SC steht für Load-Reserved / Store-Conditionally
    • Beispielsweise wird LOCK ADD [RAX], RCX in Form von LR.D, ADD, SC.D und einem bedingten Sprung erzeugt
  • Wenn die Adresse in RAX nicht ausgerichtet ist, wird es komplizierter, doch im Allgemeinen funktioniert diese Methode gut
  • Das Problem ist LOCK CMPXCHG16B
    • Diese Instruktion vergleicht RDX:RAX mit 16 Bytes im Speicher
    • Je nach Bedingung tauscht sie RCX:RBX an diese Speicheradresse aus
  • AArch64 und LoongArch64 verfügen über einige atomare 16-Byte-Instruktionen, die für die Implementierung verwendet werden können
  • RISC-V hat keine entsprechende Instruktion, sodass sich dies nicht so vollständig implementieren lässt wie auf anderen Architekturen
  • Viele Programme, darunter Unity-Spiele, verwenden LOCK CMPXCHG16B

Praktische Ergebnisse

  • Trotz der verbleibenden Einschränkungen läuft The Witcher 3 auf RISC-V mit box64
  • Die Performance erreicht im Spiel bis zu 15 fps
  • Im Hauptmenü läuft es mit voller Geschwindigkeit
  • Für ein Ergebnis auf einer Maschine, die nicht für die Ausführung von AAA-Spielen entwickelt wurde, ist das kein schlechter Stand

1 Kommentare

 
GN⁺ 2024-08-28
Kommentare auf Hacker News
  • Aus der Perspektive von jemandem, der nicht im Chip-Bereich arbeitet: Ich frage mich, was Software Engineers anders machen müssen, wenn sie Software für RISC-V entwickeln.
    Ich frage mich, ob ausführbare Dateien größer werden und man die Cache-Lokalität aggressiv optimieren muss, und ob es Arten von Software gibt, die besser zu CISC oder RISC passen, etwa Spiele oder Webserver.

    • RISC-V mit der Compressed-Instruction-Erweiterung erzeugt im Durchschnitt eher kleineren Code als x86-64 oder ARM.
      Am Softwareansatz muss man im Grunde nicht viel ändern; der größte Unterschied gegenüber x86-64 ist, dass es 32 Register gibt und man daher mehr Zwischenwerte halten kann, bevor sie auf den Stack ausgelagert werden. ARM hat ebenfalls 32 Register, ist also ähnlich. Normalerweise muss man sich darum kaum kümmern, solange man keine Mikro-Optimierungen betreibt.
      Im Detail ist die Vector-Erweiterung (V/RVV) nicht Teil der Basis-ISA rv64gc, sodass man je nach Ziel möglicherweise keine SIMD-Optimierungen bekommt. Auch popcount sowie das Zählen führender/nachfolgender Nullen sind nicht in der Basis rv64gc enthalten; dafür braucht man Zbb. Außerdem benötigt eine branchless selection wie a ? b : c in der Basis rv64gc 4–5 Instruktionen und mit Zicond 3 Instruktionen, während sie auf x86-64 und aarch64 mit 1 Instruktion möglich ist.
      RISC-V-Profile lösen die ersten beiden Probleme bis zu einem gewissen Grad. Android verlangt zum Beispiel rva23, das RVV, Zbb, Zicond usw. voraussetzt. Wenn Linux-Distributionen jedoch rva20/rv64gc als Ziel wählen, kann vorkompilierter Code ohne dynamisches Dispatching solche Erweiterungen faktisch lange Zeit nicht nutzen. Bei x86-64 gibt es ein ähnliches Problem, aber ARM hat deutlich weniger Erweiterungen und ist daher weniger betroffen; SVE ist die größte Ausnahme, wird aber noch nicht breit unterstützt.
    • In den meisten Fällen muss man gar nichts anders machen. Korrekt geschriebener Code in einer Hochsprache wie C sollte genauso funktionieren.
      Der größte Unterschied ist das schwache Speichermodell, eine Eigenschaft, die auch die meisten Nicht-x86-Architekturen wie ARM haben; Code sollte sich ohnehin nicht auf ein starkes Speichermodell verlassen.
      Die Dichte des ausführbaren Codes ist bei x86 aus historischen Gründen nicht besonders gut, daher wachsen ausführbare Dateien nicht so stark, wie man vielleicht denkt. RISC-V mit Compressed-Instruction-Erweiterung und 32-Bit-ARM mit Thumb-Erweiterung sind ziemlich kompakt.
      Entscheidend ist nicht CISC gegen RISC, sondern das Vorhandensein und die Qualität von Vektor-Instruktionen und Kryptografie-Erweiterungen. Video-Encoding/-Decoding hängt für gute Performance stark von Vektor-Instruktionen ab, und Full-Disk-Encryption oder Hashing kann von speziellen Instruktionen profitieren, die bestimmte Algorithmen wie AES oder SHA256 beschleunigen.
    • Jede Instruktionssatzarchitektur sollte für fast jede Art von Workload ähnlich gut geeignet sein. Wenn man Assembly programmiert, gibt es Unterschiede, aber wenn man etwas in Python oder Unity baut, ist das kaum ein Problem.
      Im Kern geht es eher darum, sich von ARM-Patenten zu befreien und auf Basis der gelernten Lektionen einen Neuanfang zu machen.
  • Das erinnert mich daran, wie ein bekannter Russe Atomic Heart auf einem Elbrus 8S zum Laufen gebracht hat.
    Elbrus hat einen nativen Übersetzer, und soweit ich weiß, ist der ziemlich ordentlich. Atomic Heart war mit etwa 15–25 fps einigermaßen spielbar.

  • Dem Artikel fehlt etwas die „grundlegende“ Erklärung. Ich dachte, es würde über so etwas wie einen Wine-Port laufen, aber tatsächlich sieht es so aus, als würde auf einem RISC-V-Chip auf irgendeine Weise die x86_64-ISA implementiert.
    Es wäre schön, wenn jemand diesen Teil genauer erklären könnte.

    • Die grundlegende Erklärung steht hier: https://box86.org/
      Es ist zwar ein Emulator, aber einige „System“-Bibliotheken wie libc, libm, SDL und OpenGL nutzen native Versionen, sodass es sich leicht in die meisten Anwendungen integrieren lässt und die Performance in manchen Fällen überraschend hoch sein kann. Wine kann ebenfalls nativ kompiliert und ausgeführt werden.
  • Ein beeindruckendes Ergebnis. Das ist enorm viel Arbeit, und in einigen Fällen scheint man an die Grenzen von RISC-V zu stoßen.
    Instruktionen zum Sammeln/Verteilen von Bits sollten wohl als Erweiterung aufgenommen werden.

    • Ich würde auch gern Testergebnisse mit Spielen sehen, die stärker vom Grafikkern als von der CPU abhängen. Divinity 2 wäre vielleicht geeignet.
  • Interessant ist, dass RISC-V im Kontext der x86-Emulation unter den drei unterstützten Architekturen die geringste Ausdrucksstärke hat.
    Im Informatikgeschichtsunterricht habe ich RISC als „Reduced Instruction Set Computer“ kennengelernt, aber wenn man sich heutige RISC-V-Profilvorschläge oder Texte dazu ansieht, heißt es oft: „Für funktionale Gleichwertigkeit braucht es nur ein paar zusätzliche Befehle.“ Ich verstehe, dass RISC-V für viele eine bequeme Alternative zu anderen Plattformen ist, frage mich aber auch, ob das bedeutet, dass der RISC-Traum gestorben ist.

    • Ich verstehe echtes RISC weniger als „absolut minimalen Befehlssatz“, sondern eher als: „Keine cleveren Features für den Komfort von Assembly-Programmierern einbauen, sondern möglichst viel dem Compiler statt dem Frontend-Silizium überlassen.“
      Soweit ich mich an die RISC-V-Spezifikation erinnere, war sie ziemlich strikt darin, keine „Kombi“-Befehle hinzuzufügen, weil gängige Befehlssequenzen im Frontend fusioniert werden können.
      Die Defizite von RISC-V gegenüber x86/ARM scheinen mir weniger aus RISC-Fundamentalismus zu stammen, sondern eher daher, dass die Spezifikation bei sehr einfachen Embedded-Chips begonnen hat und im Laufe der Zeit Erweiterungen für Application-CPUs hinzugefügt wurden. Im Basis-RV32I gibt es nicht einmal Integer-Multiplikation. Leider hat es viel zu lange gedauert, die Debatten um Bitmanipulation und SIMD-/Vektor-Erweiterungen zu beenden, und dadurch sind die Funktionslücken entstanden, über die wir jetzt sprechen.
    • Wenn man einen Befehlssatz schaffen will, den Studierende innerhalb eines Semesters implementieren können, braucht es Vereinfachungen, etwa dass jeder Befehl zwei Eingaben und eine Ausgabe hat. Auch für Forschende, die mit Prozessordesigns experimentieren, wird es deutlich einfacher.
      Allerdings fallen dadurch einige bequeme Befehle weg, die für hohe Leistung nützlich wären.
      Eine einfache Pipeline hat auch den Vorteil, dass sie weniger Engineering-Ressourcen eines Teams für ein High-Performance-Design verbraucht und damit mehr Zeit für Optimierung bleibt.
      RISC ist im Allgemeinen eine Philosophie der Vereinfachung, aber das Ausmaß variiert. MIPS ist ähnlich stark vereinfacht wie RISC-V, während ARM und POWER kompromissbereiter sind und im High-Performance-Bereich offenbar keine großen Probleme haben, mit x86 zu konkurrieren.
      Der Prozessormarkt hat neben der Ausführung von Anwendungen noch viele andere Nischen, etwa Embedded und Beschleuniger. In der speziellen Nische der Application-Cores bin ich bei RISC-V etwas pessimistisch, aber breiter betrachtet hat es großes Potenzial, könnte einige kommerzielle Nischen dominieren und ist als Lehr- und Forschungswerkzeug hervorragend.
    • Der RISC-Traum bestand darin, CPU-Design zu vereinfachen, weil die meiste Software nicht direkt in Assembly, sondern mit Compilern geschrieben wird.
      Klassische RISC-Merkmale sind, dass die meisten Datenmanipulationsbefehle nur auf Registern arbeiten, Speicherbefehle im Wesentlichen nur Load/Store in Register sind und daher viele Register nötig sind. Weil man den Stack für die Parameterübergabe direkt manipulieren muss, baut man auch den Stack selbst, und statt CALL-/JSR-Befehlen implementiert man das mit grundlegenden Befehlen, die direkt in das Instruction-Pointer-Register laden bzw. daraus speichern. Die Befehlskodierung ist vorhersagbar, und alle Befehle haben dieselbe Größe. In mehreren RISC-Architekturen gab es außerdem ein Register, das immer 0 liest und nicht beschrieben werden kann; es wurde verwendet, um Werte auf 0 zu setzen.
      Dieser Ansatz funktionierte, aber später haben Out-of-Order Execution und SIMD seine Bedeutung verringert. Der rohe Befehlsstrom ist eher eine Deklaration des Wegs zum gewünschten Ergebnis und bedeutet nicht, dass die CPU ihn tatsächlich genau so ausführt. Dahinter stehen spekulative Ausführung, Branch Prediction und Register Renaming. SIMD ähnelt eher einem breiten Registerraum und Befehlen, die auf alle Werte darin wirken. Am Ende haben Out-of-Order Execution und SIMD die Führung übernommen.
    • Ich frage mich, ob es überhaupt so etwas wie den RISC-Traum gibt. Es gibt den Traum von Effizienz, den Traum von Performance, den Traum von Kosten, und auch den Traum von geringer Komplexität im Verhältnis zu Kosten, Performance und Effizienz. Aber gibt es jemanden, dem RISC an sich wichtiger ist als Kosten, Performance, Effizienz und Einfachheit?
    • In diesem Kontext geht es darum, Code, der bereits für x86_64 kompiliert wurde, auf RISC-V auszuführen. Die Forderung „Für funktionale Gleichwertigkeit braucht es nur ein paar zusätzliche Befehle“ entsteht, weil man Code ausführen will, der bereits für Architekturen mit solchen Zusatzbefehlen kompiliert wurde.
      Theoretisch würde beim Kompilieren des ursprünglichen Quellcodes für RISC ein völlig anderes Binary entstehen, und diese speziellen Befehle wären möglicherweise nicht nötig.
      Realistisch gesehen wird aber wohl niemand diese Spiele tatsächlich für RISC-V kompilieren.
  • Im Screenshot werden 31 GB RAM angezeigt, was klar über der Maximalausstattung des erwähnten Developer-Boards liegt. Wird hier etwas anderes verwendet?

    • Es ist das ältere Board Pioneer.
      Heutzutage wäre man wahrscheinlich besser mit einer der neueren Optionen bedient, die mehrere schnellere Kerne mit RVA22 und RVV 1.0 implementieren.
    • https://milkv.io/pioneer
    • Das milk-v pioneer hat 128 GB RAM.
  • Ist das 86Box? Es hat Spaß gemacht, mich an die Zeit zu erinnern, als ich einen Amstrad PC1512 gekauft habe.
    Mit zwei 500-MB-Hardcards und einer 128-KB-Speichererweiterung auf 640 KB wurde es deutlich interessanter. Am Anfang hatte er nur zwei 360-KB-Diskettenlaufwerke, und ein paar Jahre später habe ich eine 32-MB-Hardcard hinzugefügt. Borland TurboPascal und Zortech C hatte ich auch. Das waren schöne Zeiten.

    • Nein, das ist Box64 und ein völlig anderes Projekt.
      An die Zeiten mit dem Amstrad PC1512 erinnere ich mich aber auch.
  • Ich frage mich, ob irgendwann ein System erscheint, das ein paar große RISC-V-CPUs zusammen mit einer „GPU“ enthält, die aus einem Bündel kleiner RISC-V-CPUs aufgebaut ist.
    Vermutlich mit passenden Vektor-Funktionen; als Nebenfrage frage ich mich auch, ob ein klassischer Vektoransatz statt packed SIMD in GPUs nützlich sein könnte.

  • Eine technisch beeindruckende Witcher-3-Leistung war auch der Switch-Port, und der lief wirklich gut.
    Er zeigt, wie viel sich durch Optimierung erreichen lässt und wie viele Ressourcen auf dem PC durch schlechte Optimierung verschwendet werden.

    • Ein großer Faktor war auch, dass deutlich geringer aufgelöste Texturen und 3D-Modelle verwendet wurden und dadurch viel weniger RAM für Assets nötig war.
      Das ist kein Vergleich auf Augenhöhe, und wenn sich der auf dem Bildschirm dargestellte Umfang stark unterscheidet, ist es schwierig, pauschal von schlechter PC-Optimierung zu sprechen.
    • Wenn man die Rendering-Auflösung auf 720p senkt, im Handheld-Modus auf 540p, die Einstellungen unter das Minimum dreht und etwa 30 fps als akzeptabel bezeichnet, kann man Witcher 3 auch auf einem PC mit Mindestanforderungen ähnlich laufen lassen.
  • Es wäre gut, wenn solches Feedback auf ISA-Ebene bei den Leuten von RVI ankäme

    • In der Scalar Efficiency SIG werden bereits Befehle zum Einfügen/Extrahieren von Bitfeldern diskutiert
      Als ich gestern nachgesehen habe [1], ließ sich das Beispiel aus dem Artikel bereits mit vier RISC-V-Befehlen umsetzen. Allerdings ist es etwas knifflig, darauf zu kommen
      # a0 = rax, a1 = rbx
      slli t0, a1, 64-8
      rori a0, a0, 16
      add a0, a0, t0
      rori a0, a0, 64-16
      [1] https://www.reddit.com/r/RISCV/comments/1f1mnxf/box64_and_ri...
    • Davon ist nichts neu
      Dass Bitfeld-Extraktion fehlt, ist eigentlich ein so offensichtlicher Fehler, dass es mein Lieblingsbeispiel dafür ist, wie absurd die RISC-V-ISA ist. Das zweite ist, dass es keinen vernünftigen Adressierungsmodus gibt
      Einige der besseren RISC-V-Designs implementieren dafür tatsächlich eigene Befehle. Zum Beispiel BEXTM von Hazard3: https://github.com/Wren6991/Hazard3/blob/stable/doc/hazard3....