Zen 5s 2-Ahead-Branch-Predictor: Wie eine 30 Jahre alte Idee neue Möglichkeiten eröffnet
(chipsandcheese.com)- 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
- “Multiple-block ahead branch predictors” by Seznec et al. – ASPLOS 1996: Behandelt Begründung und Umsetzungsrichtung des 2-Ahead Branch Predictor
- “Optimization of Instruction Fetch Mechanisms for High Issue Rates” by Conte et al. – ISCA 1995: Behandelt die Optimierung von Instruction-Fetch-Mechanismen für hohe Issue-Raten
- “Increasing the instruction fetch rate via multiple branch prediction and a branch address cache” by Yeh et al. – ICS 1993: Behandelt die Erhöhung der Fetch-Rate durch Multiple Branch Prediction und einen Branch Address Cache
- “Out-of-Order Instruction Fetch using Multiple Sequencers” by Oberoi and Sohi – ICPP’02: Behandelt Out-of-Order Instruction Fetch mit mehreren Sequencern
- “Parallelism in the Front-End” by Oberoi and Sohi – ISCA 2003: Behandelt die Parallelität im CPU-Frontend
1 Kommentare
Hacker-News-Kommentare
Zur Sprungvorhersage gibt es als gut verständlichen Artikel von den frühen Implementierungen an https://danluu.com/branch-prediction/
[0]: https://www.youtube.com/watch?v=nhXevKMm3JI&list=PLzH6n4zXuc...
[1]: https://www.youtube.com/watch?v=nczJ58WvtYo&list=PLzH6n4zXuc...
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.
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.
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.
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.
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.
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...
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.
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.
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.
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
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-
iframemeist das eigentliche SorgenkindUnterschiedliche 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-EinbettungDas 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
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
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
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
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
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
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
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
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...
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
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.
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