Doping für CPUs - Möglichkeiten der Leistungssteigerung

mj

Technische Administration, Dinosaurier, ,
Mitglied seit
17.10.2000
Beiträge
19.529
Renomée
272
Standort
Austin, TX
titel.png

Einleitung​

Einen neuen Computer zu kaufen, ist etwas schönes. Man investiert oftmals viel Geld in teure Hardware, in der Hoffnung, dass diese möglichst lange ihren Dienst leistet. Für den Heimanwender oder Firmen-PC ist dies auch der Fall, neue Hardware kann hier bis zu fünf Jahre lang die Bedürfnisse des Besitzers durchaus befriedigen.
Nicht so jedoch, wenn aktuelle Computerspiele in den Mittelpunkt des Einsatzes rücken, oder gar Anwendungen wie Videobearbeitung, 3D-Rendering oder wissenschaftliche Berechnungen. Denn plötzlich kann der Computer gar nicht mehr schnell genug sein, es muss immer das schnellste und somit auch teuerste sein. Denn Zeit ist ja bekanntlich Geld.
Um wieder auf den Heimanwender zurückzukommen: Dieser profitiert hiervon natürlich enorm. Die - vor allem in den letzten Jahren - enorm gestiegenen Anforderungen an moderne Computer/Hardware haben dazu geführt, dass heutzutage bereits 2+ GHz Computer im Supermarkt erhältlich sind und über Rechenpower verfügen, über die vor einigen Jahren die Wissenschaftler noch im Kreise gesprungen wären.
Jedoch ist bald die Grenze erreicht, denn es gibt einen Feind bei der sich ewig weiter drehenden Performancespirale: Die Physik. Immer höhere Taktraten wandeln immer mehr Energie in Wärme um, immer kleinere Strukturen erhöhen die Energie pro Fläche, sowie nähern die Strukturen dem Atomdurchmesser an.

Dass diese Taktspirale sich nicht ewig weiterdrehen kann, wissen auch die beiden größten Halbleiterfirmen Intel und AMD: Bei zukünftigen Produkten will man zu anderen Mitteln greifen, um die Gesamtleistung zu steigern. Der Gigahertzwahn scheint abzukühlen, es kehrt Ruhe und Vernunft ein in den Labors der Chiphersteller. Zeit, einen Blick auf vorhandene Hebel und zukünftige Techniken zu werfen, die vorhandene Transistoren möglichst effizient auszunutzen und somit die Leistung steigern, ohne die Energieumwandlung in Wärme auf neuen Spitzenwerte zu treiben.
[BREAK=Die Situation heute und mögliche Hebel zum Zweck der Leistungssteigerung]
Die Situation heute hat die Prozessoren bereits recht nah an die Grenzen der Physik gebracht: Mit zukünftigen Strukturen von 90nm und weniger, nähern wir uns mehr und mehr dem Durchmesser von Atomen; die Wärmeabgabe der aktuellen Flaggschiffe von Intel und AMD liegt bei rund 70W pro cm2 und der Aspekt der angemessenen Kühlung rückt unaufhaltsam immer weiter ins Zentrum der Aufmerksamkeit.

Jedem Ingenieur ist klar, dass die Situation recht angespannt ist und alternative Lösungen dringend gefunden werden müssen. Es existieren einige Hebel, um die Performance effektiv steigern zu können:

Taktfrequenz
Die Flaggschiffe von Intel und AMD nähern sich kontinuierlich der 3 GHz Marke (Intel real, AMD per Quantispeed). Der K7 Kern scheint hier mittlerweile an seiner Grenze angelangt zu sein, taktmäßig sind keine signifikanten Weiterentwicklungen mehr zu erwarten. Beim P4 Kern sieht die Situation deutlich besser aus, schließlich war dieser von Anfang an nicht auf Effizienz ausgelegt (der Pentium 4 ist bei gleicher Taktfrequenz deutlich langsamer als sein Vorgänger), sondern auf hohe Taktraten. Vorsichtige Abschätzungen sprechen von etwa 4 GHz, die der P4 Kern erreichen könnte. Ob in seiner jetzigen Form oder ein weiteres Mal überarbeitet, sei mal dahingestellt

Es scheint also, als ob Intel die besseren Karten hätte. Mittelfristig betrachtet jedoch steht AMD besser da, schließlich lauert hier der "Hammer", AMD nächste Prozessor-Generation, bereits in der Pipeline und steht relativ kurz vor seiner Veröffentlichung. Nicht die 64-Bit Erweiterung des zukünftigen AMD-Prozessors wird jedoch die Geschwindigkeit machen, ebensowenig hohe Taktfrequenz. Entscheidend wird die Effizienz des K8 Core werden: Möglichst viel mit möglichst wenig erreichen.
Der Pentium 4 ist grade mal zwei Jahre alt, seine ursprüngliche Taktfrequenz hat sich mittlerweile mehr als verdoppelt und dennoch wird er bald am Ende sein. Die Geschichte der Mikroprozessoren zeigt, dass es etwa fünf bis sieben Jahre dauert, bis ein vollständig runderneuertes Prozessordesign marktreif und auch wirklich fertig ist. Der Nachfolger des Pentium 4 wird sich also entweder noch einiges an Zeit lassen müssen, oder - wie der Pentium 4 ursprünglich auch - frühreif auf den Markt geworfen und erst im Laufe der Zeit so richtig auf Touren gebracht werden müssen.

Steigerung der IPC
Die IPC, ausgesprochen "Instructions per clock cycle" gibt an, wieviele Instruktionen der Prozessor pro Taktsignal gleichzeitig abarbeiten kann. Je höher dieser Wert ausfällt, desto höher der Durchsatz an Instruktionen und desto besser die Leistung des Prozessors. Die IPC kann durch verschiedene Hebel beeinflusst werden, ist jedoch in sich selber ebenfalls ein Hebel zum Zweck der Performancesteigerung. Ein möglicher Ansatz wären beispielsweise das Hinzufügen von Funktionseinheiten.

Auch hier steht AMD etwas besser da als Intel. Der Pentium 4 war - wie bereits erwähnt - von Anfang an nicht auf Effizienz, sondern hohe Taktraten ausgelegt. Im Gegensatz hierzu steht der AMD K7, der auch trotz niedrigerem Takt dem Konkurrenten in den meisten Aufgaben gleichwertig wenn nicht gar überlegen ist. Diese Tatsache bringt AMD seit dem Athlon XP mit dem eingeführten P-Rating (Performance-Rating) zum Ausdruck. Der Prozessor wird beispielsweise trotz 1.8 GHz realem Takt als XP 2200+ verkauft, um dem Käufer näherzubringen, dass ein Prozessor aus dem Hause der Konkurrenz mindestens mit 2.2 GHz getaktet sein muss, um mit diesem Prozessor konkurrieren zu können.
Ganz so real sind diese Werte allerdings nicht, skaliert doch der Pentium 4 bei höherem Takt deutlich besser. Auch hängt die tatsächliche Geschwindigkeit sehr stark von den verwendeten Benchmarks ab: Mit einigen kommt der K7 besser zurecht, mit anderen der P4. Nicht nur deshalb bekam AMD bei der Einführung der QuantiSpeed Zahl auch kräftigen Gegenwind aus dem Expertenlager zu spüren.

Im Hinblick auf die Zukunft kann man über die IPC noch nicht sonderlich viel sagen. In den Intel-Labs wird derzeit ein Mobilprozessor entwickelt, der sich als wahres IPC-Wunder herausstellen könnte. Der sog. Banias soll bei bis zu 1 GHz niedrigerem Takt seinem großen Bruder Pentium 4 teilweise deutlich überlegen sein.
Seitens AMD wird mit dem K8 höchstwahrscheinlich ebenfalls eine gesteigerte Effizienz zu erwarten sein, was man weiterhin durch eine QuantiSpeed-Zahl zum Ausdruck bringen will. Letztendlich bringt eine Diskussion jedoch jetzt noch nicht viel, bevor die Prozessoren nicht verfügbar sind, kann man weder auf die Effektivität noch die Effizienz schließen.
[BREAK=Mögliche Hebel zum Zweck der Leistungssteigerung (Bus- und Speichertakt)]
Erhöhung des Bus- und Speichertakts
Eine weitere recht populäre Art die Leistung zu steigern, ist das Verhältnis zwischen internen und externen Takt zu verringern. Heutige Prozessoren verfügen in der Regel über mehrstufige Cache-Hierarchien. Der Level 1 Cache (L1 Data und L1 Code) befindet sich direkt bei den Funktionseinheiten, hat eine sehr niedrige Latenz sowie einen sehr hohen Durchsatz. Zugriffe auf den L1 Cache erfolgen, je nach Prozessor, innerhalb drei bis acht Taktzyklen. Man nennt dies die sogenannte "Latenz".
Dahinter sitzt, mittlerweile ebenfalls auf dem Die integriert, der Level 2 Cache. Dieser verfügt über eine deutlich höhere Latenz von über zehn Taktzyklen und einen, gegenüber dem L1-Cache, recht geringen Durchsatz. Der L1 Cache ist in der Regel "Write Through", so dass alle Informationen aus dem L1 Cache auch gleichzeitig in den L2 Cache geschrieben werden.
Dahinter wiederum sitzt ein eventuell vorhandener externer Level 3 Cache, was sich bei Desktop-Prozessoren jedoch bisher bis auf den PPC75xx (kurz: G4) nicht durchgesetzt hat. An letzter Stelle steht nun der eigentliche Arbeitsspeicher selber.

Zugriffe auf den Speicher sind derart organisiert, dass sie immer vom schnellsten zum langsamsten Cache erfolgen. Erst wenn ein "cache miss" auftritt (cache miss bedeutet, dass die angeforderten Daten sich in keinem der Caches befinden), wird auf den Arbeitsspeicher zurückgegriffen. Moderne Speichermodule werden mit bis zu 533 MHz betrieben (PC1066 RDRAM), in der Regel sind heutige PCs jedoch mit dem weitaus günstigerem, jedoch etwas langsamerem DDR-SDRAM ausgestattet, welches laut Spezifikation bis zu 166 MHz schnell sein darf.
Der Zugriff auf den Arbeitsspeicher läuft bei allen heute verfügbaren Prozessoren über den Chipsatz, ergo über den Front Side Bus, kurz FSB. Dieser beträgt beim Athlon effektiv 266 oder 333 MHz (DDR-FSB), beim Pentium 4 effektiv 400 oder 533 MHz (QDR-FSB) und bei Apple's aktuellen Macintoshs zwischen 100 MHz SDR (iMac, eMac, iBook) über 133 MHz SDR (PowerMac DP867, PowerBook) bis hin zu 166 MHz SDR (PowerMac DP1000 und DP1250). So hoch diese Zahlen auch erscheinen mögen, im Vergleich zu 2 GHz Prozessortakt ist dies im besten Falle grade mal ein Verhältnis von 4:1. Bei Speicherzugriffen muss der Prozessor also hunderte von Zyklen mit Nichtstun vergeuden, bis die Daten bereit liegen und verarbeitet werden können. Je niedriger also das Verhältnis zwischen internem und externem Takt, desto weniger Taktzyklen muss der Prozessor bei Speicherzugriffen warten (wiederum im Verhältnis zwischen internem und externem Takt betrachtet) und desto früher steht er für weitere Berechnungen bereit.
Aus diesem Grund ist ein 1 GHz Prozessor der mit 166 MHz FSB betrieben wird schneller, als der gleiche Prozessor wenn er mit 133 MHz FSB betrieben wird.

Als kleiner Nachsatz sei noch hinzugefügt, dass eine Erhöhung des Speichertakts nicht zwingend zur Leistungssteigerung führen muss. Ein sehr gutes Beispiel für Sinnlosigkeit stellen die aktuellen PowerMacs von Apple dar. Der Arbeitsspeicher wird im DDR-Verfahren betrieben, es werden also zwei Bit pro Taktsignal übertragen. Im Vergleich zu den QuickSilver PowerMacs, also den direkten Vorgängern der heutigen Modelle, stellt dies eine Verdopplung der RAM-Bandbreite dar und müsste, obiger Erklärung zufolge, aufgrund des auf die Hälfte verringerten Verhältnisses zwischen internem und externem Takt zu einer deutlichen Leistungssteigerung führen.
Jedoch gibt es zwischen dem Dual 1 GHz mit SDR-SDRAM und dem mit DDR-SDRAM nur minimale Unterschiede. Der Grund liegt in der Art und Weise, wie der Prozessor auf den Arbeitsspeicher zugreift. Denn damit er auch die volle Bandbreite des Arbeitsspeichers nutzen kann, muss er mit eben dieser Bandbreite darauf zugreifen können. Beim AMD Athlon ist dies dank dem EV6 Protokoll möglich, der Zugriff auf den DDR266/DDR333 Arbeitsspeicher erfolgt mit 266/333 MHz. Beim G4 Prozessor in Apple's PowerMac sieht die Situation jedoch erbärmlich aus, der Zugriff auf den DDR266/DDR333 Arbeitsspeicher erfolgt hier lediglich mit 133/166 MHz wie bisher auch. Sinnvoll wäre gewesen, auch das Übertragungsprotokoll zu verändern und nicht nur die Bandbreite des Arbeitsspeichers zu erhöhen.

Vergrößerung und Optimierung der Caches
Kamen die ersten verfügbaren x86 Prozessoren noch ohne jeglichen Cache, so befinden sich heute kombiniert bis zu über einem halben Megabyte dieser schnellen Zwischenspeichern im Prozessorkern (bei Serverprozessoren auch deutlich mehr). Der Grund ist ganz einfach und wie bereits erwähnt: Um die unsäglich langsamen Speicherzugriffe möglichst zu vermeiden, werden die benötigten Daten vom Prozessor bereits vor deren Notwendigkeit in den Cache geladen und bereitgestellt. Von zu Beginn wenigen Kilobyte L1 Cache, verfügen heutige Serverprozessoren über dreistufige Cache-Hierarchien mit bis zu über 128MB kombiniertem Cache (so beispielsweise der IBM Power4 Prozessor mit 128MB L3-Cache, bis zu 48MB L2-Cache (kombiniert) und über 1MB L1-Cache (kombiniert).

Mit dem Pentium 4 hat Intel ein neues Funktionsprinzip des L1 Instruction Cache eingeführt, den sog. Trace Cache. Seit dem 6x86 arbeiten x86-Prozessoren extern wie CISC-Prozessoren mit komplexem Befehlssatz, decodieren jedoch jede Instruktion in RISC-artige µOps vor ihrer Ausführung. Dieser Vorgang der Decodierung kostet natürlich Rechenzeit und um diese zu sparen, speichert der Pentium 4 im 12.000 µOps fassenden L1 Trace Cache die bereits decodierten RISC-artigen Befehle für die weitere Verwendung ab.
Ohne diesen Trace Cache wäre der Pentium 4 übrigens nochmals deutlich ineffizienter. Warum folgt im weiteren Verlauf des Artikels.
[BREAK=Mögliche Hebel zum Zweck der Leistungssteigerung (Superskalare Arbeitsweise)]
Superskalare Arbeitsweise
Die von-Neumann Architektur für Mikroprozessoren definiert einen Computer sehr genau. So hat ein von-Neumann Prozessor strikt seriell und immer nach bestimmtem Schema zu arbeiten. Wer mit der von-Neumann Architektur noch nicht vertraut ist, dem sei empfohlen, dem Link zu folgen, schließlich stellt diese Architektur bis heute die Grundlage jeglicher Mikroprozessoren dar.

Mit einem strikt dieser Architektur folgenden Mikroprozessor würde man jedoch niemals eine IPC > 1 erreichen können. Die Tatsache, dass heutige Prozessoren einen IPC >> 1 haben, gründet schlicht und ergreifend auf der Tatsache, dass die Ingenieure die von-Neumann Architektur nach eigenen Vorstellung etwas verbogen haben und den Mikroprozessoren, beginnend mit dem Intel Pentium (586), die superskalare Arbeitsweise beigebracht haben.
Ein superskalarer Mikroprozessor unterscheidet sich hauptsächlich im Leitwerk von einem nicht-superskalaren Mikroprozessor. So teilt dieses einer Recheneinheit nicht mehr lediglich eine Instruktion pro Takt zu, sondern gleich mehrere unterschiedliche Instruktionen für mehrere unterschiedliche Recheneinheiten. Der Mikroprozessor ist somit in der Lage, mehrere Instruktionen parallel abzuarbeiten, solange diese oder ihr Ergebnis nicht voneinander abhängig sind. Somit kann ein zweifach superskalarer Mikroprozessor bei gleichem Takt im Idealfall doppelt so viele Instruktionen pro Sekunde abarbeiten, wie ein serieller Mikroprozessor.
Dieser Effekt wird erreicht, indem an unterschiedlichen Stellen der Mikroprozessor optimiert wird. So muss beispielsweise der Decoder mehr als nur eine Instruktion pro Taktzyklus decodieren können, es müssen mehrere voneinander unabhängige Recheneinheiten vorhanden sein und auch die interne Bandbreite muss stimmen. So versauern beispielsweise im Falle des Pentium 4 pro Taktzyklus sechs Recheneinheiten, da der Trace-Cache lediglich einen Durchsatz von 3 µOps/Takt erreicht.

Die erste Implementierung superskalarer Arbeitsweise fand im 586 (Intel Pentium) statt, der grundsätzlich nicht mehr ist, als ein superskalarer 80486 mit zweistufigem Decoder. Die sog. U-V Struktur des Decoders wurde alsbald von der 4-1-1 Struktur des Decoders des 686 (Intel Pentium Pro), der nun dreistufig arbeitet, abgelöst.

Out-of-order Execution
Eine weitere Möglichkeit der Laufzeitoptimierung ist die sog. Out-of-Order Execution (zu Deutsch: "Ausführung außerhalb der Reihenfolge"). Sie bietet einen enormen Zuwachs an Geschwindigkeit, denn plötzlich können Instruktionen bereits ausgeführt werden auch wenn sie noch gar nicht an der Reihe wären (Stichwort: Serieller Programmfluss der von-Neumann Architektur). Um dies besser verständlich zu machen, ein kleines Beispiel:
Nehmen wir an, dass eine Instruktion Daten aus dem Speicher einlesen muss. Dies führt dazu, dass der Prozessor für mehrere hundert bis tausend Taktzyklen auf die Daten warten muss und quasi zum Nichtstun verdammt ist. Gibt es jedoch in der Pipeline Instruktionen, die von der derzeit auf Daten aus dem Speicher wartenden Instruktion weder abhängen noch mit ihr in Zusammenhang stehen, kann der Prozessor die nötige Wartezeit mit der Ausführung dieser Instruktionen überbrücken.
In Zusammenhang mit hohem ILP und TLP (siehe folgendes Kapitel) kann eine enorme Verbesserung der Performance, ohne notwendige Eingriffe seitens des Programmierers oder Compilers, erreicht werden.
[BREAK=Mögliche Hebel zum Zweck der Leistungssteigerung (Pipelining)]
Pipelining
Eine weitere Eigenschaft, ohne die moderne Prozessoren nur einen Bruchteil ihrer Leistung entfalten könnten, ist das sog. Pipelining. Es basiert auf dem Prinzip der gegenseitigen Unabhängigkeit der einzelnen Abarbeitungsschritte eines von-Neumann Prozessors. Dieser arbeitet die Zyklen des Instruction-Fetch, Instruction-Decode, Instruction-Execute und Instruction-Retirement strikt seriell ab, wohingegen ein moderner Prozessor diese drei Dinge theoretisch gleichzeitig erledigen kann, selbstverständlich für drei unterschiedliche Instruktionen.

In der Praxis sieht dies bei einem modernen Mikroprozessor folgendermaßen aus: Während der aktuelle Befehl ausgeführt wird (Instruction Execute), wird gleichzeitig die vorhergehende Instruktion in den L1 Data Cache geschrieben (Instruction Retirement), die nachfolgende Instruktion decodiert (Instruction-Decode) und die übernächste Instruktion bereits aus dem Programmfluss ausgelesen (Instruction-Fetch). Somit müssen die Befehlseinheiten im Optimalfall niemals auf Daten warten: Sobald sie mit der Berechnung einer Instruktion fertig sind, steht bereits die nächste an.

Branch Prediction
Die letzte erwähnenswerte Optimierung moderner Prozessoren ist die Branch Prediction, zu Deutsch: Sprungvorhersage.
Sprünge stellen für Mikroprozessoren mit Pipeline ein ernsthaftes Problem dar und beschäftigen weltweit hunderte von Informatikern und Ingenieuren. Bei Sprüngen im Programmfluss wird unterschieden zwischen "conditional jump" und "unconditional jump", zu Deutsch "Bedingter Sprung" und "Unbedingter Sprung". Letztere sind leichter zu erkennen als Erstere, schließlich gibt es beim Bedingten Sprung eine Bedingung, deren Erfüllung oder Nichterfüllung über den weiteren Programmfluss entscheidet.
Tritt solch ein unverhergesehener Sprung im Programmfluss auf, so muss bei einem modernen Mikroprozessor die gesamte Pipeline gelöscht und neu gefüllt werden, ein Vorgang den man "Pipeline-Flush" oder auch "Flushing the Pipeline" nennt, zu Deutsch etwa "Ausspülen der Pipeline". Je länger die Pipeline ist, desto länger dauert logischerweise dieser Vorgang, daher kommt der Sprungvorhersage bei modernen und zukünftigen Prozessoren mit 20- und mehr-stufigen Pipelines eine enorme Bedeutung zu, deren Unterschätzung für einen Mikroprozessor den frühen Tod bedeuten kann.

Eine Sprungvorhersage (Eigentlich ist dies eine falsche Übersetzung, "branch prediction" lautet korrekt übersetzt "Abzweigungsvorhersage" oder "Verzweigungsvorhersage") versucht zu erraten, welche Richtung der Programmfluss bei einem bedingten Sprung einlegen wird. Hierbei werden die verschiedenen Verzweigungsmöglichkeiten des Programmflusses genauestens geprüft (der IBM Power4 nimmt hier gar drei Prüfungen parallel vor) und nach Wahrscheinlichkeiten gewichtet. Die Verzweigung mit der höchsten Wahrscheinlichkeit wird anschließend als weiter Verlauf des Programms definiert und die Pipeline mit den entsprechenden Instruktionen gefüllt.
Doch wie das Wort "Wahrscheinlichkeit" schon sagt, gibt es keine Garantie, dass der Programmfluss die vorhergesagte Richtung auch wirklich einschlägt. Somit wird es immer den Fall geben, dass aufgrund Versagens der Sprungvorhersage-Logik, die Pipeline ausgespült und neu gefüllt werden muss.
Die perfekte Sprungvorhersage, mit 99% Treffsicherheit (oder gar mehr), hat bisher noch niemand konstruieren können, Informatiker und Ingenieure rund um den Globus beschäftigen sich mit dem Modell der "Sprungvorhersage". In angesehenen wissenschaftlichen Magazinen wie dem Dr. Dobbs Journal oder dem ACM Computing Survey erscheinen regelmäßig wissenschaftliche Publikationen, die sich dem Thema annehmen und mögliche Lösungsansätze vorschlagen.
[BREAK=Ansätze der Optimierung per Software]
Ansätze, die Leistung der Prozessoren aufs Vollste auszureizen, gibt es einige. Deren Effektivität und Verbreitung steht jedoch auf einem ganz anderen Blatt. Auch gibt es Techniken, die zwar eine hervorragende Effektivität aufweisen können, jedoch leider Änderungen an der Hardware voraussetzen. Somit stellen diese Lösungen technisch betrachtet die optimale Lösung für neue Hardware dar. Markttechnisch betrachtet ist diese Lösung jedoch alles andere als optimal, schließlich folgt im Rattenschwanz der Konsequenzen die Inkompatibilität zum bestehenden x86 Standard.
Die im folgenden vorgestellten Techniken sind geordnet, vom untersten Level, der Optimierung einzelner Instruktionen und Befehle, über Steigerung des Parallelismus von Instruktionen und Threads bis hin zu Änderung des Befehlssatzes um einen möglichst hohen TLP mit hohem ILP (siehe nächste Seite) bei optimaler Anpassung an die Hardware zu erreichen. Aber der Reihe nach.

Optimierung der Compiler auf bestimmte Befehlssätze / Generationen
Auf der untersten Ebene steht die Optimierung einzelner Instruktionen an bestehende Hardware/Befehlssätze. Früher, als Assembler noch Standardsprache für jeden Programmierer war, stellte Programmieren noch echte "Fieselarbeit" dar. Register wollten einzeln belegt, addiert oder gelöscht werden, Speicher separat zugewiesen, etc. Der Spruch "Was man nicht mit Assembler programmieren kann, muss man halt löten" besitzt erstaunlich hohen Realitätsbezug, jeder Assembler-Programmierer wird sich lächelnd erinnern.
Mit dem Erfolg der sog. hohen Programmiersprachen, änderte sich dieser Zustand jedoch schlagartig. Plötzlich war der Programmierer nicht mehr Herr über den im Endeffekt von ihm produzierten Maschinencode, sondern musste diese Kontrolle dem Compiler übertragen (to compile = übersetzen, kompilieren; Compiler übersetzen die Befehle höherer Programmiersprachen in für den Prozessor verständlichen Maschinencode). Die Programmierer selber haben hierbei keinerlei Einfluß auf die tatsächliche Laufzeit der eigenen Programme und müssen sich auf den Compiler verlassen.
Da der Mensch jedoch nicht perfekt ist und Prozessoren sich beständig weiterentwickeln, erzeugen Compiler selten perfekten Maschinencode und altern zudem relativ schnell. Dennoch darf man die Rolle der Compiler auf keinen Fall unterschätzen, denn hier liegt das größte, per Software erreichbare Optimierungspotenzial welches die zwingend erforderliche x86-Kompatibilität wahrt.

Compiler arbeiten auf eine bestimmte Art und Weise: Jeder noch so komplexe Befehl, jede noch so große, lange oder verschachtelte Schleife einer hohen Programmiersprache, kann in einzelne, sog. Elementarbefehle zerlegt werden und somit letztendlich in Maschinenbefehle. Unterschiedliche Prozessorgenerationen verarbeiten jedoch ein und denselben Maschinenbefehl unter Umständen auf komplett unterschiedliche Art und Weise mit signifikant unterschiedlicher Laufzeit. Somit muss ein guter Compiler möglichst viele verschiedene Arten der Optimierung beherrschen, je nach Generation des zu verwendenden Ziel-Prozessors. So benötigt die i686 Architektur beispielsweise eine komplett andere Reihenfolge und Anordnung der Befehle an den Decoder als die i786 Architektur und noch anders gar die K7 Architektur.
Letztendlich liegt hier das größte Potenzial, aufgrund der Inkonsistenz der Prozessoren und ihres Befehlssatzes jedoch auch das größte Konfliktpotenzial. Schließlich läuft Software, die speziell für neuere Prozessoren optimiert wurde (Beispielsweise durch Nutzung der SSE/SSE2 Einheiten oder zusätzlicher CISC/RISC Befehle) nicht immer auch auf älteren Prozessoren. Im Markt der ewig kompatiblen x86-Prozessoren ist so etwas für viele Firmen nicht vertretbar, weshalb sich proprietäre Befehlserweiterungen auf lange Sicht nicht durchsetzen konnten. Drei bekannte Beispiele sind MMX, 3DNow! und SSE.
[BREAK=Ansätze der Optimierung per Software (ILP/TLP/VLIW)]
Steigerung des Instruction Level Parallelism (ILP)
Eine Ebene über der Optimierung einzelner Instruktionen an bestehende Hardwarestrukturen und Befehlssätze steht der Instruction Level Parallelism (zu Deutsch: Parallelismus der Instruktionen). Dies ist nichts anderes als die Fähigkeit, möglichst viele Instruktionen parallel abarbeiten zu können. Je höher der ILP, desto flotter kann der Prozessor theoretisch arbeiten. Eine Steigerung kann auf verschiedene Arten erreicht werden, beispielsweise Anpassung des Programmcodes an bestimmte Prozessorarchitekturen oder massive Nutzung von Parallelisierungsmechanismen aktueller Prozessoren.
Trotz der Tatsache, dass moderne Prozessoren beim Vorgang der Parallelisierung einen großen Teil der Arbeit von sich aus erledigen (der Pentium 4 sei an dieser Stelle mal außen vor gestellt), entfällt der Löwenanteil immer noch auf den Programmierer/Compiler. Eine Anpassung des Programmcodes an bestimmte generationsübergreifende Prozessoreigenschaften kann beispielsweise den Prozessor effektiver auslasten, was zu einem höheren Durchsatz an gleichzeitig verarbeiteten Instruktionen und somit einem gesteigerten ILP führt.
Prozessoren bringen genügend Möglichkeiten, den Parallelismus zu steigern, beispielsweise Superskalare Arbeitsweise oder Out-of-Order Execution.

Die hierfür verwendeten Hebel sind beispielsweise die superskalare Arbeitsweise oder die Out-of-Order Execution.

Steigerung des Thread Level Parallelism (TLP)
Ähnlich wie der ILP, jedoch wiederum eine Ebene höher als dieser, ist der Thread Level Parallelism (zu Deutsch: Parallelismus der Threads). Ein moderner Computer muss sehr viele Aufgaben gleichzeitig erledigen. So wird beispielsweise während dem Download einer Datei auf die Festplatte auf dieser gleichzeitig ein Anti-Viren Check vorgenommen, während der MP3-hörende Anwender fröhlich an einem Text tippt.
All diese einzelnen Anwendungen haben dutzende von einzelnen Threads gleichzeitig offen, die, um eine optimale Performance zu erreichen, möglichst parallel abgearbeitet werden (jeder dieser Threads führt pro Sekunde selber wiederum tausende bis Millionen von Instruktionen aus). Diesen Faktor der parallelen Abarbeitung von Threads bezeichnet der TLP. Selbstverständlich kann es - wie beim ILP auch - auch hier zu Konflikten zwischen einzelnen Threads oder zu gegenseitigen Abhängigkeiten kommen, die Aufgabe des Compilers liegt an der optimalen Anpassung des Programmcodes an die Gegebenheiten der Hardware

Letztendlich kann auch per Hardware der TLP deutlich erhöht werden, beispielsweise durch mehrere Prozessoren, in Hardware integriertes Multi-Threading oder andere Techniken, auf die ebenfalls im weiteren Verlauf des Artikels noch genauestens eingegangen wird.

VLIW: Very long instruction word
Ein letzter Ansatz der Parallelisierung per Software ist die sog. VLIW-Technik. Diese Art der Optimierung per Compiler ist gleichzeitig die erfolgsversprechendste als auch die radikalste Lösung. Nicht nur braucht der erzeugte Programmcode zwingend speziell angepasste und auf die Software optimierte Hardware, er ist auch noch dazu inkompatibel zum spezifikationskonformem x86 Maschinencode. Effektiv betrachtet arbeiten hier jedoch Hardware und Software Hand in Hand, um eine optimale Ausnutzung der vorhandenen Ressourcen und somit einen möglichst hohen ILP und TLP zu erreichen.

VLIW ist eine Erweiterung der superskalaren Arbeitsweise von Prozessoren und basiert auf folgender Technik:
Bei der bereits bekannten superskalaren Arbeitsweise erfolgt die optimale Anordnung und Zusammenfassung einzelner Instruktionen zur Laufzeit des Programms im Leitwerk des Mikroprozessors. Vorteile sind verhältnismäßig kurze Instruktionen und der somit niedrige Speicherbedarf der Software. Nachteil ist die niemals optimale Ausnutzung der vorhandenen Ressourcen.
Bei VLIW hingegen erfolgt die Optimierung auf die Mikroprozessorarchitektur und Verteilung der parallel ausführbaren Instruktionen auf die einzelnen Recheneinheiten bereits zum Zeitpunkt der Compilierung des Programms in Maschinencode. Der Vorgang der Übersetzung dauert dadurch zwar deutlich länger, die Laufzeiten der daraus entstehenden Programme können hierdurch jedoch drastisch reduziert werden. Ein Nachteil ist hingegen die Länge der einzelnen Instruktionen und der somit gesteigerte Speicherbedarf der Programme.
[BREAK=Ansätze der Optimierung per Hardware]
VLIW: Very long instruction word
Im letzten Absatz wurde erwähnt, dass bei der VLIW Philosophie die Hard- und Software Hand in Hand zusammenarbeiten müssen, um einen möglichst hohen Durchsatz an Instruktionen und Threads zu erreichen. Die Anpassung der Hardware an VLIW stellt somit gleichzeitig den Einstieg in das nächste und deutlich ausführlichere Kapitel der Optimierung der Hardware an bestehenden Programmcode dar.

Das folgende fiktive Beispiel ist entnommen aus dem Kapitel "Intel Itanium: 64-Bit und neue Architektur für den Servermarkt" des Artikels "64-Bit CPUs für's Wohnzimmer: Innovation oder Marketinggeblubber?", hier auf Planet 3DNow!.

<div class="newsfloatleft">
vliw.png
</div>Bei VLIW (Very Long Instruction Word) handelt es sich um eine Erweiterung der Superskalaren Arbeitsweise. Der Mikroprozessor verfügt, wie bei dieser auch, über mehrere Recheneinheiten, die auf bestimmte Operationen spezialisiert sind (ALU, FPU, etc.). Im Gegensatz zur reinen superskalaren Arbeitsweise erfolgt die Optimierung der Ausführung jedoch nicht im Leitwerk des Mikroprozessors, sondern bereits im Voraus durch den Compiler. Das Leitwerk hat nun nur noch die Aufgabe, die einzelnen Instruktionen an die einzelnen Recheneinheiten zu verteilen, nicht jedoch diese auf ihren Inhalt zu analysieren.

Im obigen VLIW-Kern eines Mikroprozessors verfügt dieser über sechs Recheneinheiten, sowie eine Instruktionslänge von 32 Bit. Ein superskalarer Prozessor würde nun ein Instruktionsregister von 32 Bit Länge besitzen, das Leitwerk dieses lesen, decodieren und anschließend an die entsprechende Recheneinheit weiterleiten. Ein VLIW Prozessor besitzt hingegen ein Instruktionsregister von

Instruktionslänge * Anzahl der Recheneinheiten

also im obigen Fall 32 Bit * 6 Recheneinheiten = 192 Bit. Eine Instruktion enthält also maximal sechs Sub-Instruktionen. Diese Einteilung erledigt der Compiler, so dass der Programmcode immer optimal auf die Maschine abgestimmt ist. Intel nennt diese Technik im Itanium EPIC (Explicitly parallel Instruction Computer), ein Compiler für IA-64 muss somit EPIC-Kompatiblen Maschinencode erzeugen.
[BREAK=Ansätze der Optimierung per Hardware (Multi-Threading)]
Time-slice Multi-Threading
Das Prinzip des Time-slice Multi-Threading (kurz TSMT) basiert auf der Tatsache, dass Prozessoren trotz der zu Beginn genannten Fähigkeiten recht viel der zur Verfügung stehenden Rechenzeit mit Nichtstun oder Warten auf Daten verbringen. TSMT unterteilt die verfügbare Rechenzeit in fixe Einheiten auf der Zeitscheibe und wechselt nach Ablauf dieser eingeteilten Rechenzeit von einem Thread zum nächsten. Obwohl auch hierbei viele Recheneinheiten des Mikroprozessors, die theoretisch parallel arbeiten könnten brach liegen, wird dennoch der Zeitverlust bei langen Wartezeiten, beispielsweise einem Cache-Miss inkl. folgendem Fetch der Daten aus dem Speicher, reduziert.

Switch-on-event Multi-Threading
Das Switch-on-event Multi-Threading (kurz SoEMT) gleicht größtenteils dem TSMT, jedoch mit der kleinen aber entscheidenden Ausnahme, dass die Rechenzeit nicht fix eingeteilt wird, sondern lediglich bei Auftreten von längeren Wartezeiten von einem Thread zum nächsten gewechselt wird. Hierbei muss jedoch der "Hauptthread" die höchste Priorität haben, bei Auftreten einer längeren Wartezeit wird zu Threads mit niedrigerer Priorität gewechselt, bis die Wartezeit für den Prozessor vorüber ist (als gutes Beispiel dient auch hier wieder ein Cache-Miss mit anschließendem Fetch der Daten aus dem Arbeitsspeicher).
Der Vorteil gegenüber TSMT ist, dass einzelne Threads höhere Prioritäten haben können als andere und somit der Programmfluss gezielt gesteuert und optimiert werden kann, wohingegen beim TSMT immer feste Rechenzeiten zugewiesen werden, nach denen der Prozessor, ungeachtet der Tatsache ob bereits eine Terminierung des aktuellen Threads vorliegt oder nicht, zum nächsten Thread schaltet.

Beide Verfahren haben jedoch den Nachteil, dass auch hierfür eine im ersten Fall dezente, im zweiten Fall deutliche Änderung des Programmcodes notwendig ist. Desweiteren sind beide Verfahren weit von der optimalen Ausnutzung der Ressourcen entfernt.
[BREAK=Ansätze der Optimierung per Hardware (Multi-Processing)]
OnChip Multi-Processing
Das onChip Multi-Processing (kurz CMP) kombiniert zwei voneinander unabhängige Prozessoren auf einem einzelnen Die. Ähnlich wie beim im noch zu erläuternden Simultaneous Multi-Threading, werden beim CMP verschiedene Ressourcen gemeinsam benutzt, andere wiederum stehen exklusiv zur Verfügung. Da in der x86-Welt (noch) kein CMP-System existiert, werde ich meine folgenden Ausführungen auf den Power4 Serverprozessor von IBM beziehen.

Der Power4 ist ein solcher CMP-fähiger Mikroprozessor, der auf einem Die zwei Prozessoren unterbringt. Im Unterschied zu einem Computer mit zwei physikalischen Mikroprozessoren müssen sich die beiden des Power4 unter anderem die Caches teilweise teilen. Teilweise bedeutet in diesem Fall, dass jeder der beiden onChip Prozessoren über einen eigenen L1-Cache verfügt, der L2-Cache (~ 1.5MB) sowie L3-Cache (32 - 128 MB) jedoch gemeinsam genutzt werden.
Dies muss jedoch nicht zwingend einen Nachteil darstellen, so kann beispielsweise bei angepasster Software ein Prozessor bereits die benötigten Daten in den L2-Cache schaufeln, die der andere in Kürze benötigen wird. Durch optimale Programmierung ist hier also tatsächlich nahezu optimale Ausnutzung der Ressourcen möglich. Dennoch darf nicht verschwiegen werden, dass bei gemeinsam genutzten Speichern immer die Gefahr des Konflikts besteht.

Der Nachteil einer solchen Umsetzung liegt jedoch auf der Hand: In Desktop-Prozessoren ist CMP unpraktikabel, da sowohl die Kosten für die Herstellung, als auch der Energieverbrauch sehr hoch sind. Ob dieser Gründe ist stark anzunehmen, dass CMP für den Markt der Heimcomputer auch weiterhin eine untergeordnete Rolle spielen wird.
[BREAK=Ansätze der Optimierung per Hardware (SMP)]
Symmetric Multi-Processing
Dies ist die wohl bekannteste und etablierteste Art, die Leistung eines Computers möglichst günstig zu steigern: Man behalte alles wie es ist und füge einfach einen zweiten Prozessor hinzu. Dass jedoch die Rechnung 2 x 1 GHz CPU = 2 GHz auf keinen Fall aufgeht, ist logisch. Auch die Tatsache, dass mehrere Prozessoren lediglich bei bestimmten, nicht heimtypischen Anwendungen wirklich Sinn machen, wird in der aufkommenden Euphorie der Taktfrequenzverdopplung gerne übersehen.
Symmetric Multi-Processing (kurz SMP) Computer haben nicht nur Vorteile und können unter Umständen sogar langsamer sein als Ein-Prozessor Systeme. Doch der Reihe nach:

Bei SMP-Systemen wird die komplette externe Peripherie behalten, lediglich die Anzahl der Prozessoren steigt. Dies bedeutet, dass weiterhin lediglich ein Speicherkanal zur Verfügung steht (respektive zwei, bei Dual-Channel Interface Chipsätzen), ein AGP-Bus und ein PCI-Bus. Verdoppelt wird hingegen die Anzahl sowie Größe der Caches, die lokalen APIC-Controller sowie die Anzahl der Prozessoren.
Ein ausbalanciertes Ein-Prozessorsystem wird also durch Verdopplung einer Einheit gehörig aus dem Gleichgewicht gebracht. Bei Ein-Prozessor Systemen gibt es unterschiedliche Schwachstellen, unter anderem den niedrigen FSB im Verhältnis zum Prozessortakt sowie den langsamen Arbeitsspeicher.
Dass die Speicherbandbreite jedoch bei Verdopplung der Prozessoren konstant bleibt, kann sich nur negativ auf die Performance auswirken. Und in der Tat ist es so, dass je klaffender das Loch zwischen Prozessortakt und Speichertakt, desto geringer der effektive Leistungsgewinn durch eine zweite CPU. Als hervorragendes Beispiel kann man die PowerMac DP von Apple verwenden. Beim Dual G4/400 oder G4/500 mit herkömmlichem PC100 Arbeitsspeicher ist der Performancegewinn gegenüber einer Ein-Prozessor Maschine deutlich spürbar, wohingegen der Dual 1 GHz mit PC133 Arbeitsspeicher kaum schneller arbeitet als die Ein-Prozessor Variante. Und das, obwohl der Speicher- als auch der Bustakt von 100 MHz auf 133 MHz angehoben wurden. Dennoch macht das Verhältnis von CPU-Takt zu Speichertakt von 7.5:1 beim Dual 1 GHz den zu erwartenden Performancegewinn einen Strich durch die Rechnung.

Die nächste Einschränkung betrifft den AGP-Bus. Für mehrere Prozessoren steht bis einschließlich AGP 2.0 lediglich ein einzelner AGP-Bus zur Verfügung. Mit AGP 3.0 hat Intel zwar die Möglichkeit der multiplen AGP-Busse spezifiziert, bis die entsprechenden Motherboards jedoch im Handel erhältlich sind, wird erfahrungsgemäß noch einige Zeit vergehen.
Bei grafisch intensiven Anwendungen wie beispielsweise 3D-Spielen kann es durchaus vorkommen, dass die Performance eines Computers sinkt, je mehr Prozessoren man hinzufügt. Der Grund liegt schlicht und ergreifend an den Konflikten und daraus resultierenden Stillständen der Prozessoren, sog. stalls (engl.: to stall = zum Stillstand bringen, stillstehen). Je mehr Prozessoren versuchen simultan auf einen AGP-Bus zuzugreifen, desto mehr Rechenzeit wird mit Stillstand vergeudet.

Der gleiche Effekt tritt im übrigen auch beim Arbeitsspeicher auf, hierfür existiert das sog. Locking (to lock = sperren, am Zugang hindern). Multi-Prozessor Kernel von Betriebssystemen müssen grundsätzlich einige zusätzliche Befehle implementieren, die beispielsweise den Arbeitsspeicher vor parallelen Zugriffen schützen, um die Datenkonsistenz zu wahren. Greift ein Mikroprozessor in einem SMP-System auf den Arbeitsspeicher zu, so wird zunächst der Zugriff auf diesen für alle weiteren, vorhandenen Mikroprozessoren gesperrt und dem wartenden Mikroprozessor der exklusive Zugriff gewährt. Anschließend muss der Arbeitsspeicher wieder für alle Prozessoren freigegeben werden.
Das Problem an diesem Prozedere ist, dass der dabei entstehende Overhead gigantisch ist. Im schlimmsten Fall kann es vorkommen, dass alle Prozessoren eines SMP-Systems sich für die Dauer des Speichersperrvorgangs im Stillstand befinden. Im Normalfall betrifft dies jedoch lediglich den auf den Arbeitsspeicher zugreifenden Mikroprozessor.
Dass dieser Vorgang, der den ohnehin schon ausbremsenden Zugriff auf den Speicher noch weiter verlangsamt, nicht sonderlich leistungsfördernd wirkt, kann sich wohl jeder, der den Artikel bis hierher durchgehalten hat, ausmalen. Das problematische an diesem Locking ist die Tatsache, dass nicht nur Single-Threaded Applications, also Programme die nicht für den Betrieb auf mehreren Prozessoren konzipiert sind, dieses Locking verursachen, sondern auch verstärkt Multi-Threaded Applications.
[BREAK=Ansätze der Optimierung per Hardware (SMP Fortsetzung)]
Symmetric Multi-Processing (Fortsetzung)
Ein weiteres Problem stellt die Kohärenz der Inhalte der unterschiedlichen Cache-Hierarchien dar. Cache-Kohärenz bedeutet die Wahrung der Gültigkeit der Inhalte der Caches. Wird beispielsweise ein Wert aus dem Arbeitsspeicher von beiden Prozessoren in den jeweils eigenen Cache geladen und von einem anschließend verändert wieder zurückgeschrieben, muss der zweite Prozessor von diesem Vorgang Wissen haben, um seine eigenen Caches entsprechend zu aktualisieren.

SMP-Systeme haben hierfür ein sog. Cache-Kohärenz Protokoll, welches für die Korrektheit der Daten sorgt.
Das Protokoll mit der höchsten Verbreitung ist das sog. MESI-Protokoll. Jeder der Buchstaben steht hierbei für einen bestimmten Zustand einer Cache-Line, die einzelnen Zustände sind wie folgt definiert:
* Modified: Der Inhalt im Arbeitsspeicher wurde seit dem spiegeln in den lokalen Prozessorcache modifiziert und stimmt somit nicht mehr überein
* Exclusive: Der Inhalt im Arbeitsspeicher stimmt noch mit dem gespiegelten Inhalt im Cache überein. Die Cache-Line befindet sich nur im Cache eines einzelnen Prozessors.
* Shared: Wie Exclusive, nur mit der Unterscheidung dass sich die Cache-Line in mehreren Caches befindet. Bei Aktualisierung der ursprünglichen Daten im Arbeitsspeicher werden alle Caches auf den neuesten Stand gebracht.
* Invalid: Der Inhalt im Prozessorcache ist ungültig, ein Zugriff erzeugt einen Cache-Miss. Die Daten müssen neu aus dem Arbeitsspeicher geladen werden.​
Die Erhaltung der Korrektheit der Daten (auf fachchinesisch Kohärenz genannt) kostet jedoch ein klein wenig Performance, was je nach Umsetzung des Protokolls unterschiedlich gravierend ausfallen kann.
Eine Erweiterung des MESI-Protokolls stellt das noch nicht so verbreitete MOESI-Protokoll dar. Es ist im Grunde genommen zum MESI-Protokoll 100%ig kompatibel, kennt jedoch noch einen zusätzlichen Status:
* Owner: Zeigt an in welchem Cache welches Prozessors sich die Daten befinden. Somit können andere Prozessoren bei Bedarf diese Daten aus dem Cache anfordern und sparen sich den dazu im Vergleich sehr langsamen Hauptspeicherzugriff.​
<div class="newsfloatleft">
moesi.png
</div>Hierbei passiert folgendes: Stellt CPU1 einen Read-Request auf einen bestimmten Speicherbereich (1) wird dieser in der Regel direkt aus dem Arbeitsspeicher in den Cache der CPU geladen. Merkt die Chipsatzlogik jedoch, dass eine weitere sich im System befindliche CPU genau diesen Speicherbereich bereits im Cache hat, so wird der Read-Request vom langesamen Arbeitsspeicher auf den schnellen Cache der betreffenden CPU umgeleitet. (2)
Die betreffende CPU (im Beispiel CPU0) überträgt die Cache-Line anschließend zum Chipsatz, (3) welcher sie direkt weiterleitet an CPU1 (4). Somit werden massiv Taktzyklen eingespart die beim Zugriff auf den im Vergleich zum schnellen Cache um den Faktor hundert bis einige Tausend langsameren Arbeitsspeicher mit Nichtstun verbracht worden wären.
Weiterhin profitiert auch der Speicherbus von dieser Taktik, da weniger Zugriffe auf den Speicherbus stattfinden und er somit für andere Aufgaben verfügbar ist.

Der durch die Wahrung der Kohärenz verursachte Performanceverlust wird durch effiziente Umsetzung des MOESI-Protokolls anstelle des MESI-Protokolls wieder mehr als wettgemacht.
[BREAK=Ansätze der Optimierung per Hardware (SMP Fortsetzung)]
Symmetric Multi-Processing (Fortsetzung)
Doch so negativ die Beschreibung solcher Systeme nun auch klingen mag, in vielen Fällen bringt ein zweiter Prozessor doch ein respektables Plus an Leistung, wenngleich bei zweifelhaftem Preis/Leistungs-Verhältnis. So ist der Pentium III oder Pentium 4 gar nicht fähig, seine Arbeit im Tandem-Betrieb zu verrichten, der Athlon MP kostet im Verhältnis zu gleichschnellen Athlon XP Prozessoren deutlich mehr. Doch auch wenn AMD den Athlon XP nicht für den Einsatz in Systemen mit mehr als einem Prozessor spezifiziert, es gibt viele Meldungen über die erfolgreiche Inbetriebnahme von zwei Athlon XP oder gar Duron Prozessoren.
Im Zweifelsfall hilft genaues Studium der einschlägigen Foren und nachfragen bei Anwendern, die dies bereits erfolgreich durchgeführt haben. Da jedoch AMD lediglich den Athlon MP für den Mehr-Prozessor Betrieb testet und spezifiziert, kann es bei Dual-XP Systemen durchaus zu Problemen kommen. Da Herstellersupport bei nicht-spezifikationsgerechter Handhabung ohnehin nicht geleistet wird, bleibt dies ein Experiment mit ungewissem Ausgang.
[BREAK=Ansätze der Optimierung per Hardware (SMT)]
Simultaneous Multi-Threading
Ein letzter Ansatz der Leistungssteigerung stellt das sog. Simultaneous Multi-Threading (kurz SMT) dar. Mit Hilfe dieses Verfahrens ist es möglich, multiple Threads parallel auf einem einzigen physikalischen Prozessor auszuführen, ohne, wie bei TSMT oder SoEMT, zwischen den einzelnen Threads hin- und her springen zu müssen.

Das Prinzip und die Philosophie hinter SMT beruht darauf, mit möglichst wenig Mehrkosten und Aufwand einen zweiten logischen Prozessor auf ein physikalisches Die zu packen. Dies wird erreicht, indem einige Bestandteile des Prozessors verdoppelt werden, andere wiederum partitioniert oder gemeinsam genutzt. Die bisher einzige erhältliche Implementierung von SMT im x86-Bereich stellt der Intel Xeon MP dar, sowie der in kürzlich von Intel vorgestellte Pentium 4 mit 3.06 GHz und Hyper-Threading. Ob es einen Prozessor von AMD mit Implementierung von Simultaneous Multi-Threading geben wird ist noch offen, jedoch wird man Intel langfristig gewiß nicht nachstehen wollen.
Die folgenden Ausführungen sind ausschließlich für den Intel Xeon MP und Intel Pentium 4 HT zutreffend, da die Implementierung von SMT von jedem Hersteller anders gelöst werden kann. Es kann demzufolge auch keine generelle Aussage über die Effektivität von SMT gemacht werden. Um klarzustellen, dass meine Ausführungen lediglich auf die genannten Prozessoren zutreffen, werde ich im folgenden den Begriff verwenden, mit dem Intel seine Prozessoren auch segnet: Hyper-Threading, oder kurz HT.

Die Hyper-Threading Technologie macht, wie bereits erwähnt, aus einem physikalischen Prozessor zwei logische. Angaben von Intel zufolge wurde dies mit lediglich 5% mehr Die-Fläche erreicht und soll signifikante Performancevorteile bieten. Jeder logische Prozessor besteht aus den acht x86 GPRs, den Kontrollregistern, einem APIC (inkl. APIC Registern) und einigen Maschinenstatus-Registern. Alle restlichen, für den Betrieb benötigten Ressourcen wie Caches, Recheneinheiten, die Pipeline oder die Sprungvorhersage-Logik, müssen von den beiden Prozessoren geteilt und somit gemeinsam genutzt werden.
Das - Intel zufolge - höchste Ziel bei der Implementierung von HT in den Intel Xeon MP war es zum einen, die Ausführung von entsprechend optimierter Software zu beschleunigen, jedoch gleichzeitig die Ausführung nicht-optimierter Software nicht zu verlangsamen. Somit musste zum einen sichergestellt werden, dass ein logischer Prozessor den anderen nicht zum Stillstand bringen kann und zum anderen, dass die geteilten Ressourcen wieder vereint werden, wenn nur ein einzelner Thread ausgeführt werden soll. Somit ist auch gleich ein weiteres Feature eines Prozessors mit Hyper-Threading klar: Der Prozessor muss im laufenden Betrieb zwischen Multi-Threaded Betriebsmodus und Single-Threaded Betriebsmodus hin- und herschalten können.
[BREAK=Neue und alte Möglichkeiten der Leistungssteigerung - per Hardware
(SMT Fortsetzung)]
Simultaneous Multi-Threading (Fortsetzung)
Da eine detaillierte Abhandlung von Hyper-Threading den Rahmen dieses Artikels sprengen würde, werde ich mich auf die Eckdaten beschränken, besonders auf den Unterschied zwischen einem Prozessor mit Hyper-Threading und einem ohne Hyper-Threading.

Caches
Die Caches werden gemeinsam benutzt, das heißt jedem Prozessor steht effektiv lediglich der halbe Cache sowie die halbe Bandbreite zu. Jedoch muss man bei den Caches unterscheiden zwischen dem L1 Trace Cache sowie allen weitern Caches (L1 Data Cache, L2 Cache, L3 Cache).

Der L1 Trace Cache ist der Ersatz für den ursprünglich L1 Instruction Cache genannten Teil der L1 Cache Struktur. Im Gegensatz zu diesem, speichert der Trace Cache jedoch die bereits decodierten µOps ab. Prinzipiell ein hervorragendes Vorgehen, jedoch aufgrund der kleinen Größe des L1 Trace Cache (Intel spricht von 12.000 Einträgen, was etwa 8 KB entspricht; zum Vergleich: Der AMD Athlon XP hat 64KB L1 Code Cache) stark ausgebremst. Bei zwei logischen Prozessoren sieht die Situation noch deutlich drastischer aus, schließlich stehen hier jedem logischen Prozessor nur noch 6.000 Speicherplätze für µOps im wichtigsten aller Caches zu. Jeder der logischen Prozessoren verfügt über einen eigenen, vom anderen unabhängigen Zeiger in den Trace Cache, so dass Konflikte von Haus aus vermieden werden.
Versuchen beide logische Prozessoren gleichzeitig auf den L1 Trace Cache zuzugreifen, so wird ihnen abwechselnd Zugriff gewährt. Auch hier wird ein vorhandenes Nadelöhr nochmals verengt, schließlich kann der L1 Trace Cache grade mal drei µOps pro Taktsignal an einen Prozessor weitergeben, was sich bei zwei logischen auf theoretische 1,5 µOps pro Taktsignal verringert.
Wenn ein logischer Prozessor sich im Stillstand befindet oder keine Daten aus dem L1 Trache Cache benötigt, so hat der andere die volle Bandbreite und Zugriffszeit zur Verfügung.

Der L1 Data Cache, der L2 Cache sowie der L3 Cache hingegen, können von beiden logischen Prozessoren gemeinsam genutzt werden und alle Einträge, unabhängig davon welcher logische Prozessor den Eintrag gemacht hat, von allen logischen Prozessoren gelesen werden. Logischerweise sind hierdurch Konflikte vorprogrammiert, jedoch wurde hier seitens Intel eiskalt kalkuliert: Man nimmt bewußt den eventuell durch Konflikte verursachten Performanceverlust in Kauf, rechnet jedoch mit einem durch die gemeinsame Nutzung höheren Leistungsgewinn. So kann beispielsweise ein logischer Prozessor bereits die Daten aus dem Speicher in den Cache laden, die der andere in Kürze benötigen wird. Bei Server Applikationen ist dies übrigens ganz normales Verhalten (siehe auch Cache-Prefetch Logik des IBM Power4) und aufgrund der Tatsache, dass der Xeon MP auf den Servermarkt abzielt, ist diese Lösung vermutlich die geschickteste.
Problematisch wird die Angelegenheit beim Pentium 4 HT, da dieser auf dem SOHO-Markt abzielt. Wie er sich in der Praxis bewähren wird, muss sich jedoch erst noch zeigen.
[BREAK=Ansätze der Optimierung per Hardware (Fortsetzung)]
Simultaneous Multi-Threading (Fortsetzung)

Microcode ROM
Im Microcode ROM liegen bereits decodierte Instruktionen vor, deren Decodierung aufgrund ihrer Komplexität (zur Erinnerung: CISC = Complex Instruction Set Computer) den Prozessor unnötig lange aufhalten würde. Das Microcode ROM ist also nichts weiter, als eine Tabelle mit allen komplexen Einträgen, auf die bei Bedarf zurückgegriffen wird.
Wie beim L1 Trace Cache auch, gibt es hier zwei unabhängig voneinander operierende Zeiger, pro logischen Prozessor einen. Die Einträge sind geteilt, bei gleichzeitigen Zugriffen wird auch hier taktweise abwechselnd zugegriffen.

ITLB
Der ITLB (Instruction Translation Lookaside Buffer, schneller Pufferspeicher für Instruktionen) kommt ins Spiel, wenn eine Instruktion benötigt wird die sich nicht im L1 Trace Cache befindet. Der ITLB empfängt die Anweisung vom Trace Cache bestimmte Daten zu liefern und setzt den Instruction Pointer auf die physikalisch anzuspringende Adresse.
Jeder logische Prozessor verfügt hier über einen eigenen, unabhängigen ITLB inkl. aller benötigten Zeiger, sowie einen eigenen 64 Byte Streaming Puffer, aus dem die Daten anschließend in den L1 Trace Cache geschrieben werden.

Sprungvorhersage
Die Sprungvorhersage ist geteilt, ein Teil wurde verdoppelt, ein anderer muss gemeinsam benutzt werden. Der Return Stack Puffer wurde aufgrund seiner geringen Transistorenzahl verdoppelt, sowie der Tatsache, dass Rücksprungvorhersagen deutlich effektiver und effizienter sind, wenn sie für jeden logischen Prozessor separat verfügbar sind.

Das große Global History Array hingegen muss gemeinsam genutzt werden, die einzelnen Einträge verfügen jedoch über eine eindeutige CPU-ID und verursachen somit keinerlei Konflikte.
[BREAK=Ansätze der Optimierung per Hardware (SMT Fortsetzung)]
Simultaneous Multi-Threading (Fortsetzung)

IA-32 Decoder
IA-32 Instruktionen sind vom Standpunkt des Decoders her extrem mühselig, verfügen sie doch über eine variable Instrunktionslänge, sowohl in Länge als auch in möglichen Parametern/Optionen. Daher ist der Decoder sehr komplex und aus einer hohen Anzahl von Transistoren aufgebaut, weshalb mal sich bei Intel dafür entschieden hat, den Decoder gemeinsam zu nutzen.

Der Decoder holt sich die IA-32 Instruktionen aus dem Streaming Puffer und decodiert diese, so dass handliche und RISC-ähnliche µOps entstehen, die anschließend in den L1 Trace Cache geschrieben werden. Im Gegensatz zu seinen Vorgängern (686) und Konkurrenten (K7) arbeitet der - oftmals zurecht - als "verkrüppelt" (Darek Mihocka, Pentium 4 in Depth) beschimpfte Decoder des Pentium 4 lediglich einfach, oder anders ausgedrückt: Der Decoder des Pentium 4 kann lediglich eine Instruktion pro Taktzyklus decodieren, wohingegen der Pentium III sowie Athlon bis zu drei Instruktionen pro Taktzyklus decodieren können.
Im Grunde genommen alles halb so wild, kommen doch ohnehin die meisten Instruktionen aus dem Trace-Cache und benötigen deutlich länger als einen Tyktzyklus zum Ausführen. Der Decoder mag zwar langsam erscheinen, die Auswirkungen sind jedoch nicht so gravieren wie man zu Beginn meint.

Kommt nun jedoch ein zweiter logischer Prozessor mit hinzu, so stellt der Decoder plötzlich doch eine massive Beschränkung dar. Bei parallelen Zugriffen muss ergo zwischen den beiden logischen Prozessoren alterniert werden. Hierbei zogen es die Ingenieure vor, erst mehrere Instruktionen für einen logischen Prozessor zu decodieren und anschließend mehrere für den anderen. Bei ungeschicktem Timing kann es hier also durchaus zu längeren Wartezeiten kommen, während denen ein logischer Prozessor zum Nichtstun verdammt ist.
Den Grund für die Entscheidung, den Decoder gemeinsam nutzen zu müssen, gibt Intel mit der enormen Einsparung an DIE-Größe an. Eine Entscheidung, die dem Xeon MP und Pentium 4 HT letztlich signifikant Performance kostet.
[BREAK=Ansätze der Optimierung per Hardware (SMT Fortsetzung)]
Simultaneous Multi-Threading (Fortsetzung)

Register Renaming
Das sog. Register Renaming befindet sich seit einiger Zeit in allen Mikroprozessoren. Die RR-Logik benennt die acht GPRs (General Purpose Registers) auf physikalisch endlich vorhandene Register um. Dadurch wird die Verwendung des Stack optimiert, auf dem nicht mehr der Inhalt aller Register sondern lediglich deren physikalische Adresse gespeichert werden muss. Der Pentium III hatte hier jedoch einen schweren Bug, der dieses Feature manchmal unnütz machte und den Prozessor enorm ausbremste, der sog. "Partial Register Stall".
Die eigentliche Umwandlung der somit nur noch virtuell vorhandenen GPRs erfolgt mit Hilfe einer sog. RAT (Register Allocation Table, zu deutsch etwa Register Belegungstabelle). Da jeder logische Mikroprozessor seine eigenen Register hat, benötigt er auch seine eigene RAT, die daher doppelt vorhanden ist.

Instruction Scheduling
Hier liegt das eigentliche Herz der Out-of-Order Execution Engine. Diese sorgt dafür, dass die Instruktionen, sobald die entsprechenden Recheneinheiten verfügbar sind, weitergeleitet werden, unabhängig vom eigentlichen Programmfluss. Die einzige Einschränkung sind voneinander abhängige Instruktionen. Rechnet beispielsweise Instruktion A mit dem Ergebnis aus Instruktion B, so kann A logischerweise nicht vor B ausgeführt werden.

Der Instruction Scheduler empfängt alle Instruktionen und kümmert sich um die zeitgerechte Zuteilung an die Recheneinheiten. Genau genommen sind es fünf Instruction Scheduler, die zusammen pro Taktzyklus bis zu sechs µOps durchreichen können. Jeder Scheduler verfügt über eine eigene Scheduler Queue, welche sich die beide logischen Prozessoren teilen müssen. Hierbei wird nicht zwischen µOps des einen oder anderen Prozessors bei der Zuteilung entschieden, lediglich der Status der Instruktionen sowie die Verfügbarkeit der Recheneinheiten spielen eine Rolle. Um jedoch Gleichheit zu wahren, sind die Queues partitioniert, jeder Prozessor kann also nur eine bestimmte Anzahl an Einträgen belegen.
[BREAK=Ansätze der Optimierung per Hardware (Fortsetzung)]
Simultaneous Multi-Threading (Fortsetzung)

Besonderheiten des HyperThreading
Wie zu Beginn des SMT-Teiles dieses Artikels bereits erwähnt, war eines der Ziele für die Implementierung von Hyper-Threading, die im Vergleich zu einem gleichschnell getakteten Prozessor ohne Hyper-Threading, identische Ausführgeschwindigkeit von nicht Multi-Threaded Anwendungen.

Jedoch ist dies rechnerisch mit halb so großen Cache, halb so schnellem Decoder und den weiteren beschriebenen Begrenzungen und Beschänkungen durch gemeinsame Nutzung der Ressourcen nicht möglich. Deshalb kann der Prozessor - im laufenden Betrieb - in den sog. Single-Task Modus geschaltet werden. Im ST0/ST1 Mode (ST0 = logischer Prozessor 1 ist über HALT inaktiv gesetzt worden, ST1 = logischer Prozessor 0 ist über HALT inaktiv gesetzt worden) werden alle zuvor partitionierten oder gemeinsam benutzten Ressourcen vereint und stehen einem einzigen logischen Prozessor zur Verfügung. Der andere logische Prozessor ruht während dieser Zeit.

Erreicht wird dies durch Anwendung des HALT-Befehls auf einen logischen Prozessor. Wird beispielsweise HALT auf den logischen Prozessor 0 angewandt, so geht dieser in den Schlafmodus über und alle Ressourcen können vom logischen Prozessor 1 verwendet werden. Wird auch dieser per HALT in den Schlafmodus geschickt, so geht der gesamte Prozessor in den C1-Mode über und schlummert vor sich hin. Der Übergang vom ST0/ST1 Mode in den MT Mode (Multi-Task, zwei logische Prozessoren verfügbar) geschieht auf einen Interrupt an einen logischen Prozessor hin.

Die Kontrolle unterliegt dem Betriebssystem, weshalb Intel auch explizit darauf hinweist, dass eventuell Änderungen in den bestehenden Betriebssystemen nötig sein könnten. Zum einen muss logischerweise SMP unterstützt werden, schließlich ist ein Mikroprozessor mit Hyper-Threading Technologie von Sicht des Betriebssystems aus ein herkömmliches SMP-System.
Weiterhin sollte das Betriebssystem den HALT-Befehl explizit nutzen, wenn nur ein Prozessor benötigt wird. Denn ist dies nicht der Fall, führt das Betriebssystem einen sog. Idle-Loop durch, der jedoch einen signifikanten Teil der verfügbaren Ressourcen aufbraucht und den Prozessor dadurch auslastet. Diese Ressourcen stehen dann logischerweise dem reellen Code ausführbaren Prozessor nicht mehr zur Verfügung und sind somit verschwendet.

Eine letzte Optimierung betrifft den Einsatz von HT-fähigen Prozessoren in SMP-Systemen. Hierbei sollte das Betriebssystem Aufgaben zunächst an physisch vorhandene Prozessoren zuteilen und anschließend erst auf die logischen zurückgreifen. So wird letztendlich eine optimale Auslastung aller vorhandenen - irrelevant ob logisch oder physisch - sichergestellt.
[BREAK=Fazit]
titel.png

Ich hoffe, auf den zurückliegenden 19 Seiten einen groben Überblick über den aktuellen Stand der Technik sowie über aktuelle Entwicklungen gegeben zu haben. Zugegeben, es handelt sich hierbei lediglich um einen kleinen Abriss. Über jeden einzelnen der Unterpunkte lassen sich Bücher und Regale füllen, die jedoch im Endeffekt dann doch nur Informatiker und Ingenieure interessieren.

Wer Verständnisprobleme hat, dem empfehle ich das Planet 3DNow! Forum, im speziellen den unten angegebenen Link zu Kommentaren. Über Feedback würde ich mich sehr freuen, ebenso über Fragen zum Artikel, egal ob Verständnisfrage, Fragen zu ergänzendem Wissen oder Fragen zu technischen Aspekten.

Wer sich weiterbilden möchte, dem kann ich folgende Artikel zur genauen Lektüre empfehlen:
* 64 Bit Prozessoren im Wohnzimmer: Innovation oder Marketinggeblubber
* Pentium 4 in Depth - Teil 1 (Teil 2-4 nicht mehr empfehlenswert, Teil 1 da Analyse des P4-Kerns)​
 
Zurück
Oben Unten