Funktionelle Einheiten, Vektorinstruktionen und Mehrkernprozessoren

Da wir alle drei Konzepte heute in den Intelprozessoren und auch einigen AMD-Typen finden, mal eine historische Betrachtung was die Unterschiede sind und was wo nützt. Das älteste sind mehrere funktionelle Einheiten. Dazu hole ich erst mal aus und erkläre wie eine CPU allgemein aufgebaut ist. Man kann sie selbst bei einfachen Exemplaren, also z.B. einem 8-Bit Mikroprozessor in mehrere Untereinheiten zerlegen die jeweils eine Aufgabe haben. Da ist zum einen der Befehlsdekoder. Er stellt fest welcher Befehl hinter dem Bitmuster steckt, das gerade geladen wird und welche Register er nutzt. Die Daten selbst bekommt er von einer Einheit die mit dem Speicher kommuniziert, je nach Architektur Load/Store Einheit, Bus-Interface etc. genannt. Dann gibt es noch mindestens eine Ausführungseinheit welche die Befehle auch durchführt, bei einfachen Prozessoren meistens als Arithmetisch-logische Einheit (ALU) bezeichnet. Das ist nur die Grundmenge. Es können durchaus mehrere dieser Einheiten sein.

Schon bald kam man drauf diese Einheiten zumindest zeitweise parallel arbeiten zu lassen. Ein Befehl wird z.B. erst von der Load/Store Einheit aus dem Speicher geholt, durchläuft dann den Befehlsdekoder und dann die Ausführungseinheit. Schon bei der 8086 CPU war die Bus-Interface Unit schon fähig während Decoder und ALU arbeiteten die nächsten Bytes vorrausschauend aus dem Speicher zu laden, das nennt man übrigens Prefetch. Der rapide Geschwindigkeitseinbruch der 8088 CPU kam dadurch zustande, dass neben den Befehlen auch noch Daten von dieser Einheit geholt und geschrieben wurden und durch die halbierte Busbreite kam sie kaum noch dazu den Prefetch durchzuführen, da jeder Transfer nun doppelt so lange dauerte. Auch bei der IBM 7030 Stretch gab es schon Parallelität von Laden/Speichern und ausführen/Dekodieren.

Allerdings brauchen viele Befehle die meisten Taktzyklen beim Ausführen. Das brachte Seymour Cray auf die Idee die Ausführungseinheiten mehrmals vorzusehen. Die CDC 6600 hatte nicht weniger als zehn davon. Sie waren allerdings relativ spezialisiert so gab es eigene Einheiten für Fließkommaoperationen und Ganzzahloperationen und eine Fließkommaeinheit für Addition/Subtraktion, eine für Multiplikation und eine für Division. Der Lohn die theoretische Spitzenleistung war etwa um den Faktor 3 höher als ohne diese mehrfach vorhandenen Einheiten.

Intel führte das beim Pentium ein, der zwei Integerrecheneinheiten hatte (die Fleißkommaeinheit war nur einmal vorhanden). Bei der Ausführung gibt es nun ein Problem, das schon bei der CDC 6600 zuschlug: der Code enthält Instruktionen, die sind unabhängig voneinander und andere bauen aufeinander auf. Also in einer Operation wird ein Ergebnis errechnet und in der nächsten wird mit diesem weiter gerechnet. Das ergibt sich schon daraus das nur eine Rechenoperation pro Befehl möglich ist. Eine einfache quadratische Gleichung wie

Y = x * x + 2 * x + 4

enthält so also vier Operationen von denen immerhin x * x und 2 * x gleichzeitig ablaufen können. Das Addieren aller drei Therme geht aber nicht parallel. Beim Pentium war dies spürbar. Mit bestehendem Code für das Vorgängermodell war er 1,6 mal schneller als dieses. Achtete ein Compiler darauf, Befehle möglichst so anzuordnen das es keine Abhängigkeiten gab, so erreichte er die 2,3-fache Geschwindigkeit. Später integrierte man dies im Prozessor und nannte das Umsortieren der Befehle „Out-of-Order Execution“. Ein Haswell-EP Prozessor hat heute vier Integerrecheneinheiten und drei Fließkommarecheneinheiten. Die Zahl ist über 20 Jahre kaum angestiegen, das liegt daran, dass es in Code schwer fällt so viele nicht voneinander abhängige Rechnungen kurz hintereinander zu finden. Mehr Einheiten würden dann nicht ausgelastet werden. Auch Seymour Crays Rechner hatten maximal zwei Einheiten desselben Typs bzw. für dieselben Operanden (er trennte die Register für Ganzzahlen, Fließkommazahlen und Adressen in drei Registersätze).

Historisch als zweites kam dann das Konzept mehrere CPU einzusetzen. Der Unterschied zu mehreren funktionellen Einheiten ist das eine CPU komplett ist, also auch Befehlsdekoder und Anbindung an den Speicher umfasst. Der wesentliche Vorteil einer Mehrkern CPU liegt vor allem bei Multitasking Systemen, die schon in den Sechzigern entwickelt wurden. Dabei liefen mehrere Programme gleichzeitig. oder ein Rechner bediente mehrere Benutzer. Doch auch bei nur einem Programm bringen mehrere CPUs einen Vorteile, wenn große Teile unabhängig von anderen sind. Bei vielen naturwissenschaftlichen Problemstellungen ist dies gegeben. Da wird eine Rechenvorschrift (Programm) mit unterschiedlichen Daten durchgerechnet, die dann ein zwei oder mehrdimensionales Modell ergeben. Dann läuft ein Teil dieser Berechnungen auf einer CPU, der andere auf der anderen. Sie müssen aber miteinander kommunizieren, weil es natürlich am Rand beider Mengen Daten gibt die zwischen den beiden Teilen ausgetauscht werden müssen.

Ein weiteres Problem, das wohl dazu führte das Seymour Cray relativ spät auf diese Karte setzte, ist das der Speicher meist gemeinsam genutzt wird. Dann können sich mehrere CPU gegenseitig blockieren oder zumindest ausbremsen und auch die Bandbreite, also Transferrate wird meist aufgeteilt. Dabei ist ein Grundübel von Speicher, seit er eingeführt wurde das, das er immer langsamer als die CPUs ist.

Der letzte Schritt bei Intel sind Vektorinstruktionen. Bei Crays Rechner waren sie der zweite und das liegt daran dass diese die oben beschriebenen naturwissenschaftlichen Probleme durchrechneten. Wie erwähnt ist die Rechenvorschrift immer dieselbe aber die Daten sind immer andere. Was liegt näher einen Befehl zu entwerfen, der dann nicht zwei Zahlen verarbeitet sondern viel mehr. Bei jedem Takt wird ein Paar verarbeitet bis bei einer Cray 1 maximal 64 Ergebnisse (andere Vektorrechner bis zu 1024!) vorliegen. Der Vorteil ist das die Dekodierung aber auch das Holen der Daten und Scheiben nur einmal erfolgte. Auch die Rechenwerke waren so ausgelegt das sie jeden Takt eine neue Rechnung anstießen, auch wenn diese einige Takte brauchte bis sie das Rechenwerk durchlaufen hatte. Es konnte sogar parallelisiert werden, man musste also nicht warten, bis 64 Werte in den Registern waren sondern konnte nach zwei Werten anfangen während gleichzeitig das Holen der Daten in die anderen Register weiterging.

Bei Intel ist die Konzeption eine andere. In der Form wie Cray sie implementierte bringt sie keinen Performancevorteil, da mittlerweile auch komplexe Rechnungen in einem Takt durchgeführt werden. Stattdessen hat bei AVX, bzw. SSE ein Befehl mehrere Datenworte (Single Instruction, Multiple Data) die in überlangen Registern (bis zu 256 Bit obwohl Fließkommazahlen nur 64 oder 32 Bit breit sind) parallel verarbeitet werden. Anstatt zwei Fließkommazahlen werden so zweimal vier oder zweimal acht auf einaml verarbeitet.

Doch das geht meist nur wenn der Code stimmt. Selbst heute sind Compiler sehr dumm. Das ist ein Ausschnitt aus der Berechnung der Gravitationswirkung von Körpern gegeneinander:

for I:=low(SSystem) to high(SSystem) do
begin
  SSystem[I].Ax:=0;
  SSystem[I].Ay:=0;
  SSystem[I].Az:=0;
  J:=0;
  while J<=high(SSystem) do
  begin
    if I<>J then
    begin
      Dx:=SSystem[I].Pos.Px-SSystem[J].Pos.Px;
      Dy:=SSystem[I].Pos.Py-SSystem[J].Pos.Py;
      Dz:=SSystem[I].Pos.Pz-SSystem[J].Pos.Pz;
      D2:=Sqr(Dx)+Sqr(Dy)+Sqr(Dz);
      D:=Sqrt(D2);
      G:=(SSystem[J].Masse*Grav)/D2;
      SSystem[I].Ax:=SSystem[I].Ax+(G*Dx/D);
      SSystem[I].Ay:=SSystem[I].Ay+(G*Dy/D);
      SSystem[I].Az:=SSystem[I].Az+(G*Dz/D);
    end;
  end;
end;

Dieser Code ist eigentlich super geeignet sowohl für mehrere Funktionseinheiten (unabhängige Berechnung von DX,DY,DZ und AX,AY,AZ). Wie auch mehrere Kerne (so kann man einen Lauf von 1 bis n/2 und einen von n/2+1 bis n aufteilen). Auch Vektoroperationen sind möglich, denn es sind in allen Durchgängen die selben 9 Berechnungen. Compiler sind aber noch heute so dämlich, dass man, damit sie die Instruktionen nutzen, dann jede Berechnung in eine Schleife packen muss also so umschreiben:

      while J<=high(SSystem) do
      Dx[j]:=SSystem[I].Pos.Px-SSystem[J].Pos.Px;
      J:=0;
      while J<=high(SSystem) do
      Dy[j]:=SSystem[I].Pos.Py-SSystem[J].Pos.Py;
      J:=0;
      while J<=high(SSystem) do
      Dz[j]:=SSystem[I].Pos.Pz-SSystem[J].Pos.Pz;

Das bedeutet man muss auch die eigentlich nur kurzzeitig als Zwischenwerte benötigten Variablen DX, DY und DZ in einem Array speichern. Es verwundert nicht, dass von dem Performanceboost von AVX, der bei dem achtfachen der normalen Geschwindigkeit liegen sollte wenig in realen Anwendungen übrig bleibt. Vor allem aber dominieren diese Anwendungen nicht bei den PC-Usern, sie profitieren eher von mehr Kernen, noch mehr aber von mehr Takt da typisch ein Thread sehr viel Performance braucht und andere im Hintergrund mit weniger Leistung auskommen. Daher führte Intel noch vor AVX den Turbo-boost ein. Das ist das „Übertakten“ eines Kerns während die anderen wenig zu tun haben. Er nutzt damit das Thermalbudget aller Kerne aus. Wird ein Kern höher getaktet so steigt die Wärmeabgabe meist stark an. Bei Supercomputern finden diese Erweiterungen dagegen ihren Einsatz. Allerdings haben sie dort Konkurrenz von GPUs bekommen die das Problem anders lösen: sie setzen einige Hunder bis 1000 einfache Recheneinheiten auf genauso viele Zahlenpaare an.  Ihre Leistung ist daher erheblich höher, auch wenn sie schwerer zu programmieren sind und der Speicher noch mehr zum Flaschenhlas wird.

5 thoughts on “Funktionelle Einheiten, Vektorinstruktionen und Mehrkernprozessoren

  1. War nicht auch bei Intel diese Vektorrechnergeschichte der zweite Schritt? Meines Wissens nach war doch schon MMX eine solche Vektoreinheit, lange bevor die Multicores kamen (ok Mehrere Prozessoren gab es auch schon recht früh daher kann ich das nicht mit Gewissheit sagen).

  2. MMX war eine Zweckentfremdung der FPU indem man dort mehrere 8 oder 16 Bit Integerzahlen gleichzeitig berechnen konnten, aber kein SIMD (die Daten waren immer noch so lang wie normale 64 Bit Fließkommazahlen, wurden nur anders interpretiert. Wenn man von Vektoroperationen spricht dann meint man mehrere Fließkommazahlen. Allenfalls 3DNow könnte man so interpretieren, da konnte man zwei 32 Bit Fließkommazahlen gemeinsam interpretieren.

    Wenn ich Intels Nomenklatur selbst folge ist Vektorarithmetrik erst mit AVX eingezogen vorher hieß das ganze Streaming SIMD Extensions

  3. Sehr interessant. 🙂
    Ich bin nach dem lesen dieses Beitrags zu dem verlinkten 8086-Artikel auf der Homepage gesprungen und von da zum „Intel outside“-Artikel. Dabei fiel mir auf, dass manches, was dort als aktuell beschrieben steht, inzwischen überholt ist. Also solltest Du zumindest dabei schreiben, wann der Artikel entstanden ist, wenn schon keine weitere Überarbeitung geplant ist. – Wie alt der Artikel schliesslich ist, fiel mir auf, als im Abschnitt über die Alpha-Prozessoren vom „aktuellen Windows 2000“ die Rede war. – Windows 2000? – das sind dann also etwa 14 Jahre, oder sagen wir mal 9 Jahre, da der Mainstream Support von W2K bis Sommer 2005 lief.

    Jetzt noch zu den Optimierungen durch den Compiler. Zumindest bei C, bzw. C-plusplus-Compilern hat man eine riesige Batterie an Optionen, womit man auch die Optimierung steuern kann. Sollte es damit nicht möglich sein, dass auch der Compiler erkennen kann, wo sich was parallelisieren lässt? – Oder ist man dafür immernoch zwingend auf Erweiterungen angewiesen, die dem Compiler mitteilen, wo es was zu parallelisieren gibt? – Gibt doch von Intel sowas, komme nur gerade nicht darauf, wie es sich nennt…

  4. @Bernd: Ja streng genommen stimmt das, ich hatte das auch nicht mehr ganz im Kopf, ist ja auch schon etwas länger her.
    @Hans: Ja Intel hatte da was weil die Itanium-Prozessoren das Problem mit den vielen parallelen Recheneinheiten und der Optimierung ziemlich stark auf den Compiler abgewälzt haben, was neben anderen auch einer der Gründe für den Niedergang dieser eigentlich überlegenen Prozessorarchitektur war.

  5. @Hans: Da ich über 1.400 Seiten habe, habe ich es aufgegeben alle zu aktualisieren, da käme ich nicht mehr nach. Seit etwa 2 Jahren steht unten am Artikel immer das Erstell- und letzte Aktualisierungsdatum, bei allem was älter ist steht nichts.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.