Bernd Leitenbergers Blog

Prozessorarchitekturentwicklung

Im heutigen Artikel will ich knapp mal die Grundlagen der internen Optimierung von Prozessoren aufgreifen. Das Thema ist nicht neu, ich habe es auf meiner Webseite in mehreren Artikeln ausführlich behandelt.

Der Befehlszyklus

Ein Mikroprozessor der ersten Generationen, aber auch viele größere alte Computer arbeiteten nach dem Befehlszyklus. Die Ausführung jedes Befehls unterteilte sich in drei elementare Operationen:

Jeder Befehl besteht mindestens aus diesen drei Phasen, sie können sich aber auch wiederholen. Sind Daten Bestandteil des Befehls, so kommt mindestens ein weiterer Fetch-Zyklus hinzu, um diese zu holen. Komplexe Befehle belegen mehr als ein Byte und dann steht der Befehl nach dem ersten Decode nicht fest und es kommt noch ein Fetch / Decode hinzu und zuletzt benötigen nur einfache Befehle einen Ausführungszyklus. Komplexe Befehle können davon mehrere benötigen. Bei mathematischen Operationen sind das z.B. Multiplikation oder Division. Für jeden Mikroprozessor kann man so für einen Befehlsmix, den man anhand typischer Programme bestimmen kann angeben, wie viele Takte er typischerweise für eine Befehlsausführung braucht. Beim Urahn der x86 Reihe, dem 8086 waren dies 10 Takte. Bei maximal 8 MHz bei seiner Einführung konnte er also 800.000 Instruktionen pro Sekunde ausführen – diese Zahl (0,8 MIPS) war früher eine grobe Leistungsangabe. Grob nur deswegen, weil die Befehle der verschiedenen Prozessoren unterschiedlich waren und so nicht direkt vergleichbar.

Die Pipeline

Intern zerfällt ein Prozessor in Untereinheiten mit jeweils einer bestimmten Funktion. Das können z.B. sein:

Das sind nur einige mögliche Einheiten. Die erste Erkenntnis der Prozessorbauer war, dass bei dem obigen Zyklus bei jeder Phase nur eine dieser Einheiten aktiv war. Beim Fetch eben die Busansteuerung und bei Decode der Befehlsdecoder. Bei Execute hing es vom Befehl ab. Die anderen Einheiten hatten nichts zu tun. Bei einer Pipeline ist das anders. Da holt bei jedem Takt die Fetch Einheit ein Byte aus dem Speicher, der Dekoder dekodiert ein Byte und die Ausführungseinheiten führen den Befehl aus. Da der Befehl so von Station zu Station wandert, nennt man das Pipeline.

Das ganze klingt, aber einfacher als es ist und hat in der Praxis einige Tücken. So kann die Adresse wechseln, wenn z.B. ein Unterprogramm aufgerufen wird. Dann wurden schon Daten aus dem Speicher geholt, die man gar nicht brauchte. Trotzdem kann eine Pipeline die Ausführung drastisch beschleunigen. Bei der x86 Serie wurde sie mit dem 80286 eingeführt, bei Großcomputern Mitte der Sechziger Jahre. Ein prominentes Beispiel war die IBM 360 Serie.

Wie groß eine Pipeline ist, das hängt von der Architektur ab. Beim 80286 hatte sie drei Stufen also drei Befehle konnten gleichzeitig bearbeitet werden. Bei Großrechnern mit RISC-Architektur stellte man in den Sechzigern fest, dsas das Optimum für diese Befehle bei sechs Stufen lag. Demgegenüber erreichten einige Versionen des Pentium 4 eine Pipeline von 32 Stufen – das war dann doch etwas zu viel und heutige Prozessoren haben zwischen 14 und 19 Stufen. Rein theoretisch kann man so mit eiern Pipeline die Ausführungszeit auf minimal 1 Takt pro Befehl drücken, das hatte man bei der x86 Architektur mit dem 80486 erreicht, der 80 % aller Befehle in einem Takt ausführen konnte.

Superskalar

Der Begriff „superskalar“ ist etwas verwirrend. Es ist so ein typisches Fachwort. Skalare sind bei Computern einfache Daten, also eine Zahl, ein Byte oder eine Adresse. Das Gegenteil davon ist der Vektor, eine Datenstruktur aus vielen Skalaren, wir würden dazu sagen ein Feld wie man es auch direkt in Programmiersprachen (meist mit dem Schlüsselwort „Array“ deklarieren kann. „Super“ bedeutet in Latein „über“, es ist also mehr als eine skalare Architektur. Gemeint ist damit schlicht und einfach, dass die oben erwähnten Funktionseinheiten mehrfach vorhanden sind.

Will man mehr als einen Befehl pro Takt ausführen, so ist logisch, das diese Einheiten mehrfach vorhanden sein müssen, denn nur so kann man mehr als einen Befehl pro Pipelinestufe bearbeiten. Bei Großcomputern wurde das ebenfalls Mitte der Sechziger Jahre eingeführt, einer der ersten Rechner war die CDC 6600. Bei der x86 Linie hatte der Pentium erstmals eine Einheit verdoppelt, das war die ALU, welche auch die meisten Operationen ausführen muss.

Doch auch hier gibt es Probleme. So können Befehle voneinander abhängen. Bei jeder Befehlszeile in einer höheren Programmiersprache, die mehr als eine mathematische Operation beinhaltet, ist das der Fall, denn Prozessoren können (meist) nur eine Operation pro Befehl durchführen. Dann gibt es aber eine Abhängigkeit – die zweite Operation braucht das Ergebnis der ersten um weiter rechnen zu können. Bei:

D = A+ B + C

muss man zuerst A+B addieren, bevor man zum Zwischenergebnis C addieren kann. Das hält dann wieder auf. Die Lösung bei komplexeren Prozessoren ist eine eigene Einheit, der Scheduler, der Buch führt, welche Einheiten wie belegt sind und die Befehle dann auf die Einheiten verteilt. Er kann Befehle so vorziehen, wenn wie oben ein Befehl auf ein Ergebnis warten muss. Das nennt man „out of Order execution“. Das ist ein mächtiges Werkzeug. Innerhalb der x86 Serie haben alle Prozessoren dieses Feature bis auf die Atoms. Wer jemals einen Computer mit einem Atom (z.B. ein billiges Notebook oder Tablett) und einen iCore Prozessor hatte ,weiß wie unterschiedlich schnell die bei ähnlichem Takt sein können. Seit der Haswell-Generation hat ein x86 Prozessor übrigens 14 Funktioneinheiten, die ALU ist als häufigste Einheit nicht weniger als sechsmal vorhanden. Maximal 4 Befehle pro Takt können ausgeführt werden.

Mehrere Kerne

Verhältnismäßig spät wurden bei der x86 Architektur mehrere Kerne eingeführt. Der große Unterschied – der ganze Prozessor ist mehrfach vorhanden. Dazu gehören neben den Funktionseinheiten bei heutigen Prozessoren auch die Caches – Zwischenspeicher, weil bezahlbares DRAM schon seit 30 Jahren zu langsam für Prozessoren sind, Register aber auch die gesamte Verbindung zur Außenwelt.

Mehrere Kerne verlagern die Logik, wie man die Beschleunigung von Programmen verbessern kann, vom Prozessor auf das Betriebssystem. Es ordnet jedem Kern einen Prozess zu. Selbst wenn nur ein Benutzer am Computer sitzt, können das viele Prozesse für Systemaufgaben sein. Bei Großrechnern, die aber meist viele Benutzer bedienen, zogen Mehrkernprozessoren viel früher ein, da man so praktisch jedem Kern einen oder mehrere Benutzer zuweisen konnte. Auch ein einzelnes Programm kann so beschleunigt werden, weil oft in einer Schleife immer die gleiche Operation auf unterschiedlichen Daten durchgeführt wird, dann kann man jeden Schleifendurchlauf einem anderen Kern zuweisen. Auch dies verlagert die Logik auf eine höhere Ebene, diesmal auf die Entwicklungsumgebung welche das Programm in einer höheren Sprache wie C in den Maschinencode überträgt. Der Nachteil: erfolgt das nicht, dann wird nur ein Kern benutzt.

Ein Zwischending ist SMT – Symmetrical Multithreading. Bei Intel auch als Hyperthreading bezeichnet. Das ist im Prinzip ein Prozessor, der dem Betriebssystem mehr Kerne meldet als er tatsächlich hat, z.B. acht anstatt vier. Intern nutzt er in den vier Kernen unbenutzte Einheiten, um damit die Befehle der nicht existierenden vier Kerne abzuarbeiten. Das ist also eine interne Optimierung der Auslastung. Der Nachteil: gegenüber einem echten Kern beträgt der Gewinn an Geschwindigkeit nur 25 %. Beim obigen Beispiel wäre der Prozessor also so schnell ein Prozessor mit fünf Kernen.

Immer mehr Kerne

Als Intel 2005 den ersten Mehrkernprozessor einführte, versprach die Firma das sich jede Generation, also alle zwei Jahre, die Kernanzahl verdoppeln würde, man heute also 256 Kerne haben müsste. Dem ist nicht so. Xenons, Prozessoren für Server sind mit bis zu 28 Kernen zu haben. Beim Desktop stieg die Kernanzahl sogar nur langsam und lag bis vor drei Jahren bei maximal sechs. Durch AMD und seine Ryzen-Architekltur ist da etwas Bewegung gekommen.

Das grundlegende Problem bei mehr Kernen ist das jeder Kern natürlich seine eigenen Signalleitungen hat. Es gibt pro Kern mindestens 64 Daten- und 44 Adressleitungen, dazu etliche Leitungen mit Spannungen und für Signale oder Handshhakes. Aktuell haben die gängigen Fassungen über 1.000 Anschlusspins. Das ist nicht einfach steigerbar. So ruderte Intel auch beim Xenon Phi zurück, der mal 256 Kerne haben sollte, als er mit mehrjähriger Verzögerung herauskam, waren es schließlich 50. Die Serverprozessoren haben mehr Kerne, weil auch der Chipsatz mehr Speicherriegel zulässt, acht oder 16 pro Board. Bei der iCore Serie ist bei maximal vier Speicherslots Schluss und der Speicher muss die Daten auch liefern. 28 Prozessoren benötigen eben siebenmal mehr Daten pro Zeit als vier und entsprechend mehr Speicherkanäle, jeden mit seinem eigenen Bus der dann weitere Anschlusspins benötigt.

Grafikkarten zeigen wie eine mögliche Lösung aussehen könnte. In der Architektur unterscheiden sie sich leicht von den CPU. Sie haben nicht mehr Kerne, dafür sehr viele, bis über Tausend Funktionseinheiten, die meist jedoch viel einfacher aufgebaut sind als die von Prozessoren. Sie sind auf eine massiv parallele Abarbeitung von immer gleichen Daten getrimmt, nicht auf die universelle Ausführung beliebiger Programme. Die Daten holen sie daher über sehr breite Busse (bis zu 512 Bit pro Zugriff anstatt 64) und damit dies schnell geht, ist das RAM direkt angebunden, fest auf die Karte verlötet, wie dies bis 1990 auch bei den PC der Fall war.

Vielleicht geht der Trend auch wieder zurück zu diesem Fall, dass man eben ein Mainboard mit mehreren festen RAM-Ausbaustufen anbietet, dafür es aber einen viel breiteren Bus zum Prozessor gibt, der ebenfalls dann fest verlötet auf dem Board ist, was die Zwischenstation über die Pins die man aus mechanischen Gründen nicht beliebig verkleinern kann, umgeht.

Historisch wurde die Pipeline Anfang der sechziger Jahre eingeführt. Die IBM 7094 von 1963 gilt als einer der ersten Rechner mit einer Pipeline. Mehrere funktionelle einheiten kamen nur wenig später, die CDC 6600 gilt als erster Vertreter. Sie wurde im September 1964 vorgestellt. Schwerer ist es zu benennen wann es mehrere Kerne gab. Die Abgrenzung zu mehreren funktionellen Einheiten ist schwer. Der ILLIAC 4 war im November 1975 der erste Rechner mit vielen Prozessoren, wird wegen der großen Zahl (256) aber eher als erster Vertreter der massiv multiparalellen Rechner angesehen. In dem Sinne, das ein Computer mehrere Einheiten hat, die aber auch isoliert alleine als Prozessor arbeiten können fällt mir persönlich die Cray X/MP ein mit anfangs zwei, später 4 Prozessoren, jeder war eine Cray 1. Sie teilten sich aber einen gemeinsamen Speicher.

Bezogen auf die x86 Linie war es so, das ein 8086 ohne Pipeline 10 Takte für einen Befehl brauchte. Die Pipeline brachte eine Reduktion auf 1 Takt/Befehl für die meisten befehle beim 80486, also der Faktor 10. Die heutigen Architekten die sich seit Haswell nicht wesentlich geändert haben verarbeiten durch mehrere parallele Einheiten 2,5 Befehle im Schnitt pro Takt, also ein weiterer Faktor 2,5 und die Kernzahl ist variabel je nach Prozessortyp. Im Konsumer-Segmente sind 4 bis 6 Kerne üblich. In der Summe kommt man so auf eine Steigerung um den Faktor 100 bis 150. Noch mehr brachte die Erhöhung des Taktes – heute sind 4 GHz und mehr üblich, der 8086 arbeitete mit 8 MHz, das ist der Faktor 500.

Die mobile Version verlassen