1 Punkte von GN⁺ 2024-07-28 | 1 Kommentare | Auf WhatsApp teilen
  • AMD Zen 5 führt im Zuge einer umfassenden Neugestaltung der Zen-Architektur einen 2-Ahead Branch Predictor ein und holt damit Forschungen zur mehrblockigen vorausschauenden Sprungvorhersage aus den 1990er Jahren zurück in die moderne Erweiterung des x86-Frontends
  • Die Branch Prediction sorgt dafür, dass das CPU-Frontend weiter Instruktionen holen kann, noch bevor das Ergebnis eines bedingten Sprungs feststeht; Fehlvorhersagen führen zu Pipeline-Flushes und Neustartkosten
  • Zen 5 setzt im 32KB großen L1-Instruktionscache auf zwei 32-Byte/Zyklus-Fetch-Pipelines, die jeweils mit einem 4-wide-Decode-Cluster verbunden sind; auch der Op Cache ist dual-ported mit 6-wide aufgebaut und versorgt die Op Queue mit bis zu 12 Operanden
  • Die neue Struktur verarbeitet pro Zyklus zwei taken branches über nicht zusammenhängende Instruktionsblöcke hinweg und nutzt ein 5-Bit-Längenfeld, um den Startpunkt des dritten Vorhersagefensters zu finden und so eine Überbelegung von Decode- und Op-Cache-Ressourcen zu verringern
  • Weil x86 wegen variabler Befehlslängen die parallele Bestimmung von Instruktionsgrenzen erschwert, werden der 2-Ahead Branch Predictor und die doppelte Fetch- und Decode-Struktur zur zentralen Änderung beim Ausbau des Zen-Frontend

Was sich im Zen-5-Frontend geändert hat

  • AMD beschreibt Zen 5 als umfassende Neugestaltung der Zen-Architektur, und der 2-Ahead Branch Predictor ist einer der auffälligsten Teile dieser Änderungen
  • Die Struktur greift eine Idee aus Arbeiten von vor 30 Jahren wieder auf, in einem Umfeld, in dem die Leistung einzelner Kerne erneut weiter gesteigert werden soll
  • Wenn mehrere taken branches gleichzeitig vorhergesagt werden, kann das Frontend schon weiter entfernte Teile des Instruktionsstroms im Voraus betrachten

Warum Branch Prediction für die CPU-Pipeline nötig ist

  • Moderne CPUs verwenden eine Pipeline-Struktur, die das Holen (Fetch), Decodieren und Ausführen von Instruktionen in mehrere Stufen aufteilt
  • Bei bedingten Sprüngen muss das Frontend entscheiden, welche Instruktionsadresse als Nächstes geholt wird, bevor die Auswertung der Bedingung abgeschlossen ist
  • Es gibt im Wesentlichen zwei Möglichkeiten
    • Anhalten, bis das Sprungergebnis feststeht
    • Den nächsten Pfad vorhersagen und bei Fehlern die falsche Arbeit verwerfen und an einem sicheren Punkt neu beginnen
  • Ist die Vorhersage falsch, ist ein Flush nötig, der alle auf dieser Spekulation beruhenden Arbeiten aus der Pipeline entfernt
  • Die Kosten eines Stopps an einer Sprungbedingung steigen proportional zur Anzahl der Pipeline-Stufen zwischen Instruction Fetch und dem Abschluss der Auswertung der Sprungbedingung
  • Letztlich muss die CPU den Instruktionsfluss eines Programms so genau wie möglich vorhersagen

In welchem Kontext die 2-Ahead-Idee entstand

  • Eine einfache Vorhersagemethode bestand darin, kurze rückwärts gerichtete Sprünge immer als taken anzusehen; historisch war das deutlich vorteilhafter, als immer die nächste Adresse zu holen
  • Schon das Vorhalten kleiner Zustände wie jüngerer Branch-Historie oder Adressverläufe liefert bei realen Programmen bessere Ergebnisse
  • Bereits mit Strukturen von einigen Dutzend KB waren Branch-Prediction-Raten im oberen 90%-Bereich möglich
  • Der 2-Ahead Branch Predictor ist ein seit den frühen 1990er Jahren diskutierter Vorschlag; schon damals wurde zugleich das Problem behandelt, die Architekturbreite auf 8-wide oder mehr zu erhöhen
  • Als kommerzielle CPUs später in Richtung Multicore gingen, wurde die Fläche einzelner Kerne wichtiger, und die Forschung konzentrierte sich stärker auf flächeneffizientere Verbesserungen beim TAGE predictor
  • Weil mit kleineren Fertigungsprozessen mehr Transistoren auf ähnlicher Fläche untergebracht werden können, ist zusammen mit dem Trend zu Hunderten von Out-of-Order-CPUs auch das Interesse an Single-Core-Leistung wieder gestiegen

Warum das x86-Frontend schwieriger ist

  • ISAs mit festen Befehlslängen wie 64-bit Arm lassen sich bei beliebigen Abschnitten einer Instruktions-Cache-Line leicht parallel decodieren
    • Dafür teilt man die Eingangsdaten an garantierten Byte-Grenzen von Instruktionen auf und dupliziert die Decoder-Logik
  • Bei x86 sind die Befehlslängen variabel, daher müssen die Instruktionsbytes linear geparst werden, um die nächste Grenze jeder Instruktion zu erkennen
  • Mit Pipelining, etwa durch teilweises Vor-Decodieren von Präfixen, lässt sich einiges parallelisieren, aber bei x86 ist dieser Aufwand nicht gering
  • Das ist auch ein Grund, warum 4-wide Decode bei leistungsorientierten x86-Kernen lange Zeit üblich war
  • Designs wie Intels 6-wide Decode in Golden Cove wurden durch die höhere Logikdichte moderner Fertigungsprozesse praktikabel, doch die Flächen- und Energiekosten eines monolithischen parallelen x86-Decodings steigen mit zunehmender Breite überproportional
  • Typischer Integer-Code in allgemeinen Anwendungen weist eine Branch-Dichte von ungefähr einem branch pro 5 bis 6 Instruktionen auf, was den Anreiz verringert, die Decoder-Breite noch deutlich weiter zu erhöhen
  • Das x86-Frontend braucht also eine Möglichkeit, den schwer parallelisierbaren Schritt der Bestimmung von Instruktionsgrenzen zu umgehen und zuverlässig zur nächsten Instruktionsgrenze zu gelangen

Zen 5s Implementierung mit doppeltem Fetch und Decode

  • “Multiple-block ahead branch predictors” by Seznec et al. ist die zentrale Arbeit zu Begründung und Umsetzung des 2-Ahead Branch Predictor
  • Ein Predictor, der mehrere taken branches verarbeitet, reicht allein nicht aus; Seznec et al. argumentieren, dass zur praktischen Nutzung ohne explodierende Flächenanforderungen ein dual-porting des Instruction Fetch nötig ist
  • Zen 5 stellt Instruction Fetch und Op Cache auf eine Dual-Port-Struktur um
    • Es nutzt im 32KB großen L1-Instruktionscache zwei 32-Byte/Zyklus-Fetch-Pipelines
    • Jede Fetch-Pipeline ist mit einem eigenen 4-wide-Decode-Cluster verbunden
    • Der Op Cache ist dual-ported und 6-wide ausgelegt und kann die Op Queue mit bis zu 12 Operanden versorgen
  • Auch der Branch Target Buffer wurde dual-ported
    • Dass Zen 5s L1 BTB mit 16K Einträgen so groß ausfällt, könnte die Existenz eines dual-ported L1 BTB erklären
    • Das L2 BTB hat 8K Einträge und ist damit kleiner als das L1 BTB
    • AMD verwendet das L2 BTB ähnlich wie einen Victim Cache; aus dem L1 BTB verdrängte Einträge wandern in das L2 BTB

Wie zwei taken branches in einem Zyklus verarbeitet werden

  • Zen 5 kann in einem Zyklus zwei taken branches über nicht zusammenhängende Instruktionsblöcke hinweg verarbeiten
  • Diese Änderung reduziert den Verlust an Fetch-Bandbreite bei taken branches und erlaubt Vorhersagen über zwei taken branches hinaus
  • Dadurch kann weiter in den Instruktionsstrom hinter dem zweiten taken branch geschaut werden, sodass drei Vorhersagefenster möglich werden
  • Alle drei Vorhersagefenster können genutzt werden, um Instruktionen für den Decode zu erzeugen
  • Dem zweiten Vorhersagefenster ist ein 5-Bit-Längenfeld zugeordnet
    • Es verhindert Situationen, in denen Decode- oder Op-Cache-Ressourcen überbucht würden
    • Es ist kleiner als ein Pointer, liefert aber den Startpunkt für das dritte Vorhersagefenster
    • Wenn das dritte Vorhersagefenster eine Cache-Line-Grenze überschreitet, muss kein zusätzlicher Zustand für den Prediction-Lookup-Index des nächsten Zyklus gespeichert werden
    • Wenn das dritte Vorhersagefenster innerhalb derselben Cache-Line wie das erste oder zweite Vorhersagefenster liegt, ist dieses partielle dritte Fenster nicht so wirkungsvoll wie ein vollständiges drittes Vorhersagefenster

Verbleibende Einschränkungen bei SMT

  • Wenn in Zen 5 zwei Threads aktiv sind, werden die an die Decode-Cluster gebundenen Fetch-Pipelines statisch partitioniert
  • Um in diesem Zustand wie ein Dual-Fetch-Core zu arbeiten, muss sowohl aus dem L1-Instruktionscache als auch aus dem Op Cache geholt werden
  • Dass AMD den Op Cache dual-ported ausgelegt hat, könnte dazu dienen, die Dual-Fetch-Pipeline besser aufrechtzuerhalten

Mitgenannte verwandte Arbeiten

1 Kommentare

 
GN⁺ 2024-07-28
Hacker-News-Kommentare
  • Zur Sprungvorhersage gibt es als gut verständlichen Artikel von den frühen Implementierungen an https://danluu.com/branch-prediction/

  • Ich bin gespannt, wie die SMT-Leistung ausfallen wird, und erwarte, dass dieser Ansatz Vorteile bringt und in der nächsten Generation weiter verfeinert wird.
    Zen5c geht bis 192 Kerne oder 384 vCPUs, und bei Zen 6c im nächsten Jahr scheinen 256 Kerne möglich zu sein. In einem Dual-Socket-1U-Server wären damit potenziell 512 Kerne und 1024 vCPUs möglich.
    Die Skalierungsprobleme von Web-Apps, die man 2014 hatte, würden heute in einen einzelnen Server passen, sofern die Kühlung ausreicht. Selbst mit nur 1 RPS pro vCPU wären das schon 1000 RPS, Cache-Treffer noch nicht eingerechnet, und selbst die HN-Startseite bekommt nicht 1000 Pageviews pro Sekunde auf einen Server.

    • Das Ausliefern von Webseiten ist eine günstige Aufgabe, daher stößt man wahrscheinlich eher an Netzwerk-I/O-Grenzen, bevor man die Kerne auslastet.
      Ich frage mich, wie die HPC-Leistung aussehen wird. Kühlung an sich muss kein großes Problem sein, aber ab einem gewissen Punkt könnte es zu einem nassen Problem werden, bei dem Direct Liquid Cooling (DLC) nötig ist.
    • So wie das Computing in den 1940ern in das Kilobyte-Zeitalter eintrat, treten wir jetzt in das Kilokern-Zeitalter ein.
      Wenn man eng gepackte Serverschränke mit GPUs als eine einzige Maschine betrachtet, sind wir dort bereits bei mehreren hundert Kilokernen.
      Ich hatte einmal eine ziemlich unterhaltsame Diskussion mit jemandem, der das Konzept nicht verstand, dass man einen Dienst in der Größenordnung von Wikipedia auf einem einzigen Server betreiben könnte. Das ist schon seit geraumer Zeit ohne Weiteres möglich; man macht es nur aus praktischen Gründen wie Verfügbarkeit oder Kosteneffizienz nicht.
    • Es könnte auch umgekehrt sein. Je besser man die CPU-Pipeline auslastet, desto weniger Spielraum bleibt, einen zweiten Thread einzuschieben, wodurch der SMT-Vorteil kleiner werden könnte.
    • SMT braucht deutlich mehr Benchmark-Validierung.
      Intuitiv würde man erwarten, dass es Speicherkosten geben müsste, wenn mehr Aufgaben dasselbe Problem mit halber Geschwindigkeit verarbeiten. Ich frage mich, ob Apps häufig sind, die bei aktiviertem SMT nur mehr Speicher verbrauchen, ohne Geschwindigkeitsgewinn.
      In vielen der veröffentlichten Benchmarks scheint es so, als würden die meisten Apps bei der Laufzeit keinen nennenswerten Vorteil erhalten.
    • Skalierungsprobleme bei Web-Apps entstehen normalerweise rund um die Datenbanklatenz.
  • Es ist immer interessant zu sehen, wie Jahrzehnte alte Arbeiten damals fast unbeachtet veröffentlicht wurden und dann plötzlich State of the Art werden, sobald die Hardware stark genug ist.
    Ein Beispiel ist der Z-buffer. Er wird in 3D-Videospielen verwendet, aber als er erstmals in einer Arbeit auftauchte, war er eher eine Randnotiz, weil der Speicherbedarf als zu hoch galt.
    Einige Jahrzehnte später wurden Megabytes ziemlich billig, und schließlich nutzte ihn jeder Echtzeit-3D-Renderer.

    • Ein weiteres Beispiel sind Low-Density-Parity-Check-Codes. Robert Gallager entdeckte sie 1962, aber rechnerisch waren sie unrealistisch und wurden deshalb über Jahrzehnte verworfen und vergessen.
      Es scheint, als habe es in der Literatur etwa 38 Jahre lang eine Lücke gegeben, bis David MacKay sie wiederentdeckte.
      Der erste Mainstream-Einsatz war 2003, und heute werden sie in WiFi, Ethernet und 5G verwendet.
      [1] https://en.wikipedia.org/wiki/Low-density_parity-check_code
      [2] https://scholar.google.com/scholar?q=%22low+density+parity+c...
    • Ich frage mich manchmal, ob sich für Ingenieure nicht solche akademischen Karrieren verbergen.
      Wenn man in eine Bibliothek geht und die Arbeiten liest, die Informatikforscher damals auf Papier veröffentlicht haben, findet man vielleicht Ideen, die damals unrealistisch waren, heute aber umsetzbar wären.
    • Ich glaube, es geht weniger darum, dass die Hardware stark genug geworden ist, sondern eher darum, dass solche Designs schon vor Jahrzehnten möglich waren, aber erst unter der heutigen Kombination von Trade-offs attraktiv wurden.
      In den letzten 20 Jahren wurde die Single-Core-Leistung zugunsten horizontaler Skalierung, also mehr Kerne, zurückgestellt, sodass die Komplexität und Chipfläche einzelner Kerne zum Problem wurden. Ohne diese Entwicklung, und wenn CPU-Designer primär auf Single-Core-Leistung gesetzt hätten, hätten wir solche Implementierungen vermutlich viel früher gesehen.
      Der Z-buffer ist ein einfaches Konzept, daher wirkt es plausibel, dass er in der Arbeit wie eine Randbemerkung erschien. Ein besseres Beispiel wäre vielleicht Raytracing. Auch ohne Hintergrund in 3D-Grafik ist das Konzept selbst ziemlich klar, aber bis vor Kurzem war es für Echtzeit-Rendering aus Performance-Sicht unrealistisch.
      Interessant ist, dass man keinen einfacheren Ansatz gefunden hat, der realitätsnahe Darstellung gut genug approximiert, und stattdessen zu einer alten, etwas naiven und teuren Lösung zurückkehren musste.
    • Ein weiteres Beispiel ist der Borrow Checker von Rust, der in Arbeiten über substrukturelle Typsysteme von vor Jahrzehnten wurzelt.
      Viele Wissenschaftler gingen davon aus, dass substrukturelle Typsysteme praktisch von Garbage Collection verdrängt worden seien, doch Rust hat sie in Kombination mit damals neuen Ideen aus C++ wiederbelebt.
    • Der Z-buffer benötigt nicht nur zusätzlichen Speicher in der Größe eines Framebuffers, sondern auch viel Lese-/Schreibbandbreite pro Pixel.
      Dieser zusätzliche Bedarf an Speicherbandbreite machte eine gute Implementierung schwierig und teuer. Hochwertige Implementierungen nutzten dedizierte RAM-Kanäle, während er bei günstiger Hardware einen großen Teil der Bandbreite gemeinsamer Speicherinterfaces beanspruchte.
      Einige N64-Spiele schalteten den Z-buffer beispielsweise ab und optimierten die Zeichnung von Hintergrund und Vordergrund in Software, um die Kosten für das Lesen und Aktualisieren von Tiefeninformationen zu vermeiden.
  • Spekulative Prädiktoren waren bereits Ziel mehrerer Angriffe zum Abgreifen privater Daten
    Wenn ein großer Teil gängiger ISAs verwundbar ist, frage ich mich, ob Maßnahmen ergriffen werden, um die Auswirkungen solcher Angriffe zu verringern

    • Die Schwachstelle ist nicht die Branch Prediction, sondern die spekulative Ausführung. Der Branch Predictor ist nur das Ziel, das dazu gebracht werden muss, den Prozessor im Opferprogramm zur spekulativen Ausführung von Code zu verleiten. Außerdem braucht man zum Auslesen der Ergebnisse der spekulativen Ausführung auch eine brauchbare Timing-Quelle
      Dagegen gibt es keine echte Abhilfe, außer einem Ansatz auf dem Niveau „den Ozean zum Kochen bringen“[0]. Spekulative Ausführung ist für die Performance viel zu wertvoll; ein Rechner ohne sie wäre praktisch völlig unbenutzbar. Wer wirklich einen Prozessor ohne spekulative Ausführung will, kann sich einen alten Pentium der ersten Generation kaufen
      Praktische Gegenmaßnahmen gibt es verschiedene, aber mindestens muss eine Prozesstrennung zwischen einem Opferprozess mit Geheimnissen und einem potenziellen Angreifer gewährleistet sein, der die Ausführung des Opfers beeinflussen kann
      Intel wurde dabei erwischt, spekulativ über Ring-Grenzen hinweg auszuführen, sodass man aus dem User Space heraus Kernel- oder Hypervisor-Speicher lesen konnte. Bei CPUs mit halbwegs vernünftigem Design ist HTML-iframe meist das eigentliche Sorgenkind
      Unterschiedliche Origins können zwar nicht beliebig HTTP-Anfragen senden[1], sich aber ohne Erlaubnis gegenseitig einbetten[2]. Traditionell wurden diese Informationen dann in den Angreiferprozess geladen und konnten per Timing-Angriff exfiltriert werden
      Die frühe Lösung im Web bestand nicht darin, iframes prozessseitig zu trennen, sondern Shared-Memory-Multithreading ganz zu entfernen. Nimmt man dem Angreifer den Zeitmaßstab, ist es viel weniger relevant, was das Opfer spekulativ ausführt. Das erfordert aber, Multithreading zu entfernen. Andernfalls kann ein Thread eine Uhr bauen, indem er bekannte Daten in einer Schleife wiederholt schreibt
      [0] https://hackaday.com/2013/08/02/the-mill-cpu-architecture/
      [1] Zumindest nicht, sofern die Ziel-Origin dies nicht per CORS erlaubt
      [2] Zum Beispiel Image-Hotlinking oder iframe-Einbettung
    • Ausnutzbar ist die Stelle, an der spekulative Ausführung mit virtueller Adressübersetzung und dem Cache interagiert
      Das ist keine dem Predicting selbst innewohnende Schwachstelle
  • Als Einsteiger auf diesem Gebiet ist mir auch nach dem Lesen des Artikels nicht klar, was genau ein 2-ahead Branch Predictor ist

    • Das Konzept ist ungefähr 30 Jahre alt und scheint auf dieses Paper von 1996[0] zu verweisen. Es liegt über meinem Niveau, aber es scheint bei Branch-Prediction-Problemen zu helfen, die durch mehrere Ausführungseinheiten und hohe Taktraten entstehen
      In den 90ern war es wohl eher das eine oder das andere, moderne Prozessoren haben anscheinend meist beides
      Das Abstract zu „Multiple-block ahead branch predictors“ erklärt es so, dass zur Vorhersage der Adresse des übernächsten Instruktionsblocks nicht die Informationen des aktuellen Instruktionsblocks zur Vorhersage der Adresse des nächsten Blocks verwendet werden. Dadurch lässt sich bei breit dispatchenden „brainiac“-Prozessoren der Fetch-Flaschenhals verringern, und zwei Instruktionsblock-Adressen können effizient in einem Zyklus vorhergesagt werden
      Außerdem heißt es, dass sich bei „speed demon“-Prozessoren der Branch-Prediction-Prozess pipelinen lässt, um höhere Taktfrequenzen oder bessere Genauigkeit durch größere Vorhersagestrukturen zu erreichen. Anders als bei bestehenden Multi-Predictor-Ansätzen können Multiple-Block-Ahead-Branch-Predictors jedes beliebige Branch-Prediction-Verfahren verwenden
      [0] https://dl.acm.org/doi/10.1145/237090.237169
      Ergänzend: eyegor hat den Link wohl schon gepostet, aber ich wollte damit sagen, dass man sich wenigstens das Abstract ansehen sollte
    • So wie ich es verstehe, wird nicht das Ziel des nächsten Branches vorhergesagt, sondern das Ziel des darauffolgenden Branches
      Das dürfte deutlich schwieriger sein als die Vorhersage des nächsten Branches, erlaubt aber, Code viel früher zu holen, um tiefere Pipelines zu füttern
    • Auch wer kein Einsteiger ist, ist davon genauso verwirrt. Der Artikel verbringt viel Zeit damit, die absoluten Grundlagen der Branch Prediction zu erklären, überspringt aber ausgerechnet die Erklärung von 2-ahead
    • Es scheint darum zu gehen, pro Zyklus nicht einen, sondern zwei Branches vorherzusagen
      Statt also wie bei normaler Branch Prediction nur n+1 vorab auszuwerten, kann man auch das Ergebnis für n+2 im Voraus bestimmen. Wie das funktioniert, ohne den L1-Cache durcheinanderzubringen, ist mir nicht klar
      Wenn dabei über n+1 hinausgeblickt wird, würde ich viel mehr Cache-Verdrängung erwarten, also übersehe ich offenbar etwas
      Bei Zen 5 heißt das wohl, dass weiter in den Instruktionsstrom hinter dem zweiten genommenen Branch geschaut werden kann und dadurch drei Vorhersagefenster zur Verfügung stehen, die nützlich sind, um Instruktionen zum Dekodieren bereitzustellen
      Das Original-Paper ist frei zugänglich, ich habe es aber noch nicht genauer gelesen: https://dl.acm.org/doi/10.1145/237090.237169
    • Ein normaler Branch Predictor rät, welchen Weg ein Branch, etwa ein if-else, nehmen wird, bevor er ausgeführt wird. So kann die CPU Instruktionen vorab holen und dekodieren
      Jede Richtung eines Branches führt zum Anfang eines neuen Instruktionsblocks, und die letzte Instruktion eines solchen Blocks ist meist wieder ein weiterer Branch
      Ein Branch Predictor ist also ein Mechanismus zum Erraten der Adresse des nächsten Blocks. Ein 2-ahead Branch Predictor macht dasselbe, aber für die zwei darauffolgenden Blöcke
      In den Worten des Papers: „Informationen aus dem aktuellen Instruktionsblock werden verwendet, um die Adresse des Blocks vorherzusagen, der auf den nächsten Instruktionsblock folgt“
      Anders als ein normaler Branch Predictor kann er das tun, ohne zu warten, bis die Instruktionen des nächsten Blocks dekodiert wurden. So lassen sich mehrere Instruktionsdecoder gleichzeitig versorgen
      Das ist besonders nützlich in modernen CPUs, bei denen die Instruktionsdecoder zum Flaschenhals geworden sind, weil ein einzelner Decoder, der nur eine Instruktion pro Zyklus dekodiert, kaum mit einem breiten Frontend mithalten kann, das 4 bis 6 oder mehr Instruktionen pro Zyklus ausführen kann
  • Möglicherweise braucht es mehr Branch Hints: https://github.com/ziglang/zig/issues/5177
    Vielleicht wäre etwas wie cold, warm, warmer denkbar, wobei hot der Default ist und deshalb weggelassen wird. Manchmal könnte man auch alle Branches bis auf einen als cold markieren

  • Wahrscheinlich ist das eine schlechte Idee, aber ich würde gern den Grund verstehen
    Wenn es einen bedingten Sprung gibt, frage ich mich, warum man nicht die Befehle beider möglichen Zweige holen und vorbereiten und dann die falsche Seite verwerfen kann
    Ich würde gern wissen, ob das viel schwieriger ist oder ob es andere Gründe gibt, die es nicht lohnend machen

    • Das ist eine zweitbeste Strategie
      Moderne TAGE-Branch-Predictor liegen weit über 99 % Trefferquote. Zusätzliche Instruktionen vom jeweils anderen Zweig werden also fast immer verworfen
      Noch schlimmer ist, dass das Frontend Dutzende Verzweigungen weiter voraus Instruktionen holt, bevor das Backend die tatsächliche Richtung bestätigen kann. Was macht man dann bei der nächsten Verzweigung? Vier mögliche Pfade dekodieren, dann 8, 16, 32? Das meiste davon würde man wegwerfen
      Wenn man Hardware hat, die mehrere Instruktionsströme parallel holen kann, wie bei Intel Gracemont/Goldmont/Skymont und AMD Zen 5, ist die bessere Strategie, davon auszugehen, dass der Branch-Predictor zu 100 % richtig liegt. Man folgt einem Zweig und dann dem nächsten gleich mit
      Intel Skymont hat 3 Decoder, jeweils 3-wide, und dekodiert so parallel die nächsten 3 Branch-Ziele. Intel fügt sogar künstliche Sprünge ein, um große Codeblöcke aufzuteilen, damit die drei Decoder immer unterschiedliche Teile des kommenden Instruktionsstroms dekodieren. Danach werden die 3 Mikrooperationsströme zusammengeführt, sodass Skymont eine effektive Decode-Bandbreite von 9 Instruktionen pro Takt halten kann
      Beide Zweige auszuführen würde nur in den seltenen Fällen einer Fehlvorhersage die Latenz etwas senken. Wenn man stattdessen den nächsten zwei oder drei Vorhersagen in einer Richtung weiter folgt, können Intel und AMD mehrere Decoder parallel arbeiten lassen. Intel kann mit 3 einfacheren 3-wide-Decodern auf 9-wide kommen, AMD mit 2 einfacheren 4-wide-Decodern auf 8-wide
    • Fälle, in denen das Branch-Ergebnis zufällig ist, sind selten
      Compiler, Runtime und CPU können oft richtig abschätzen, welches Ergebnis wahrscheinlicher ist, und meistens ist es die bessere Strategie, die zusätzliche Arbeit gar nicht erst zu machen. Besser, als Silizium und Wärme für die falsche Antwort zu verschwenden, nur für den Fall, dass man falschliegt
      Viele Leute haben offenbar kein Gefühl dafür, wie genau Branch-Prediction sein kann. Schon wenn man nur den eigenen Code anschaut, merkt man schnell: „Der meiste Kontrollfluss geht hier entlang, und diese Verzweigung behandelt einen Ausnahmefall“
      Moderne Compiler können das ebenfalls ziemlich gut ableiten, und CPU/JIT/Runtime können beeindruckende Heuristiken aufbauen. Wenn das trotzdem scheitert, kann man explizite Hinweise in den Code einfügen, um Compiler usw. die erwartete Richtung mitzuteilen
    • Ich arbeite nicht in dem Bereich und bin nur Enthusiast, aber Branch-Predictor scheinen immer schon zu gut gewesen zu sein, als dass sich beide Seiten gelohnt hätten
      Der Instruction Reorder Buffer moderner CPUs ist mehrere hundert Instruktionen tief, und wenn davon nur 8 bedingte Sprünge sind, ergeben sich bereits 256 mögliche Programmpfade
      Wenn die Wahrscheinlichkeit, dass der Branch-Predictor alle 8 richtig vorhersagt, über 50 % liegt — was realistisch ist —, dann lohnt sich 256-facher Aufwand zur Absicherung einfach nicht
    • Das nennt man spekulative Ausführung, und soweit ich mich erinnere, macht das jede moderne CPU
      Man braucht mehr Silizium, um zusätzlichen mikroarchitektonischen Zustand vorzuhalten, und mehr Execution Units, um die Technik voll auszunutzen. Aber superskalare CPUs brauchen so etwas ohnehin schon, um Instruction-Level Parallelism in Code mit wenigen Verzweigungen auszuschöpfen
      Der Rest ist lästige Arbeit, um komplizierte Dinge wie Aliasing oder Interrupts korrekt zu behandeln, aber Hardware-Ingenieure sind irgendwie Zauberer und bekommen auch das hin
      Spekulative Ausführung eröffnet allerdings auch die Möglichkeit, Cache-Timing-Seitenkanäle auszunutzen, um Informationen aus Daten auszulesen, die von Code berührt wurden, der nur spekulativ ausgeführt wurde und dessen architektonische Seiteneffekte nie committet wurden. Das heißt: Informationen können selbst aus Code abfließen, der „in Wirklichkeit“ nie ausgeführt wurde
      Dazu gehört auch Code, der wegen einer Bedingungsprüfung, etwa einer Berechtigungsprüfung, explizit nicht ausgeführt worden wäre
      Ein bekanntes Angriffsbeispiel ist Spectre: https://en.m.wikipedia.org/wiki/Spectre_(security_vulnerabil...
    • 90 % Genauigkeit wurden schon vor Jahrzehnten erreicht. Je nach Workload leisten moderne Chips deutlich mehr
      Deshalb rechnet sich das im Grunde überhaupt nicht. Diese Chip-Ressourcen sind in anderen Threads oder Kernen viel besser eingesetzt
  • Um beurteilen zu können, ob das eine gute Idee ist, würde ich zuerst gern Leistungsdaten sehen. Informationen zur Branch-Misprediction-Penalty dieses Ansatzes fehlen ebenfalls
    Die Intuition hinter diesem Ansatz scheint jedenfalls zu sein, Instruktionen aggressiv zu holen und zu dekodieren, die sich möglicherweise noch nicht im L1-Instruktionscache oder im Mikrooperationscache befinden
    Das ist bei x86 und vermutlich auch bei RISC-V wichtig. Bei beiden ist die Instruktionslänge variabel, sodass der Core nicht allein anhand eines Instruktionscache-Blocks wissen kann, wie er die Instruktionen darin dekodieren soll. Bei beiden ISAs muss man mindestens den PC einer Instruktion kennen, um mit dem Dekodieren eines Instruktionscache-Blocks zu beginnen
    Wenn man also weiß, wohin die Anwendung zwei Blöcke weiter springen könnte, hilft das dabei, noch weiter voraus zu holen und zu dekodieren als beim aktuellen Ansatz
    Der Ansatz ähnelt Instruction Prefetching, aber Instruction Prefetching gibt dem Core keine Information über den Startpunkt
    Bei leistungsstarken ARM-Cores besteht dieses „Startpunkt finden“-Problem wahrscheinlich nicht, weil alle Instruktionen 32 Bit lang sind. Daher könnte man den Dekodiervorgang parallelisieren, selbst wenn der Startpunkt nicht bekannt ist
    Dieses Verfahren dürfte frontend-lastigen Anwendungen Vorteile bringen, etwa Cloud-Workloads, bei denen heiße Codeblöcke über das Binärprogramm verstreut liegen. Ich frage mich, ob es bei anderen Anwendungsarten Leistungsgewinne oder -verluste gibt

  • Ich verstehe immer noch überhaupt nicht, was ein 2-ahead-Branch-Predictor ist

    • Es ist vielleicht besser, zuerst die im Artikel verlinkte ältere Forschungsarbeit zu lesen
      Im Allgemeinen setzen ältere Forschungsarbeiten eher voraus, dass die Leser über solche Themen viel weniger wissen. Damals war dieses Wissen deutlich nischiger
  • Jetzt braucht es nur noch Speicherbandbreite. Die zwei Speicherkanäle des Consumer-Sockels AM5 wirken im Vergleich zu dieser Rechenleistung ziemlich mager, selbst gegenüber einfachem Apple Silicon
    Ich bin von einer massiven Zen-Konfiguration auf einen M2 Max umgestiegen und war erneut überrascht, wie stark mehr Speicherbandbreite intensive Datenarbeit beschleunigt. Selbst bei recht schwerem Multitasking war die schmale Speicherpipeline der Zen-Konfiguration oft der Engpass.

    • Es gibt tatsächlich nur sehr wenige Anwendungen, bei denen die CPU trotz eines Speicherbandbreiten-Flaschenhalses besser geeignet ist als die GPU
      Der Grund, warum sich viele Leute gerade wegen LLMs Apple Silicon ansehen, ist, dass LLMs zwar besser auf GPUs laufen, aber auch sehr viel VRAM brauchen und NVIDIA GPUs mit viel VRAM zu absurd hohen Preisen anbietet
      Wenn AMD NVIDIAs Vormachtstellung wirklich brechen will, müsste das Unternehmen Consumer-GPUs mit 64 bis 128 GB VRAM verkaufen
    • AM5 hat tatsächlich vier Speicherkanäle. Der Grund ist, dass DDR5 die Anzahl der Kanäle verdoppelt hat.