Prozessoren

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------


Die zweitwichtigste Komponente die die Computerleistung bestimmt ist der Prozessor. Somit ist es wichtig für welchen Prozessor du dich entscheidest, damit du deine Spiele in hohen Auflösungen genießen kannst. Außerdem kannst du mit Mehrfachprozessoren viele Anwendungen gleichzeitig ausführen, ohne Leistungseinbrüche von der Software bzw. Hardware verzeichnen zu müssen.

Wie funktioniert ein Prozessor?


Ein moderner Computer ist heute ein universell einsetzbares Multifunktionsgerät. Wir können Videos aufzeichnen, DVDs abspielen, Musik hören, im Internet surfen, realitätsnahe Spiele spielen, Büroarbeiten erledigen und vieles mehr. Das Einsatzgebiet eines PCs ist inzwischen so breit gefächert, dass man beinahe vergisst, zu welcher Gerätegattung der Computer eigentlich gehört: zu den Automaten. Daher müssen wir, um zu verstehen, wie ein PC in seinem Innersten - also hinter den bunten Oberflächen mit Mauszeigern, Menüs und 3D-Action - arbeitet, die Automatenlehre und die Logische Algebra bemühen.

Automaten und Universalrechner
Automat oder Automatik nennt man selbsttätig reagierende Maschinen oder Module. Auf Basis dieser Automatik stellte im Jahre 1945 der in Ungarn geborene Amerikaner John von Neumann ein Konzept zur Gestaltung eines Rechners vor, der technischen, wissenschaftlichen und kommerziellen Anforderungen gerecht werden sollten. Dieser Rechner bestand aus den Funktionseinheiten Steuerwerk, Rechenwerk, Speicher, Eingabe- Ausgabewerk, wobei mit  Ein- und Ausgabe hier nicht Tastatur und Bildschirm gemeint sind, sondern die Schnittstellen nach außerhalb der zentralen Recheneinheit, also das Prozessors.

Per Definition ist die Struktur des Von-Neumann-Rechners unabhängig von den zu bearbeitenden Problemen, weshalb man eine solche Maschine auch Universalrechner nennt. Sie kann jedes beliebige Rechenproblem lösen und ist nicht an eine spezielle Aufgabe gebunden, wie es etwa ein Cola-Automat ist.

Zur Lösung eines Problems muss von außen eine Bearbeitungsvorschrift, das Programm, eingegeben und im Speicher abgelegt werden. Ohne dieses Programm ist die Maschine nicht arbeitsfähig. Programme, Daten, Zwischen- und Endergebnisse werden in dem selben Speicher abgelegt. Der Speicher ist in gleich große Zellen unterteilt, die fortlaufend durchnummeriert sind. Über die Nummer (Adresse) einer Speicherzelle kann deren Inhalt abgerufen oder verändert werden. Aufeinanderfolgende Befehle eines Programms werden in aufeinanderfolgenden Speicherzellen abgelegt. Das Ansprechen des nächsten Befehls geschieht von Steuerwerk aus durch Erhöhen der Befehlsadresse um Eins. Durch Sprungbefehle kann von der Bearbeitung der Befehle in der gespeicherten Reihenfolge abgewichen werden. Es gibt arithmetische Befehle wie Addieren, Multiplizieren, Konstanten laden, logische Befehle wie Vergleiche, logisches NICHT, UND, ODER, Transportbefehle, z. B. vom Speicher zum Rechenwerk und für die Ein-/ Ausgabe, bedingte Sprünge und sonstige Befehle wie Schieben, Unterbrechen, Warten usw. Von einigen wenigen Ausnahmefällen abgesehen, orientieren sich die heutigen PCs an der Struktur dieses klassischen Universalrechners.

Der Von-Neumann-Zyklus
Der Prozess der Befehlsverarbeitung bei Von-Neumann-Rechnern wird Von-Neumann-Zyklus genannt und besteht aus folgenden fünf nacheinander ablaufenden Teilschritten: FETCH, DECODE, FETCH OPERANDS, EXECUTE, UPDATE INSTRUCTION POINTER. Beim FETCH-Schritt wird aus RAM- oder ROM-Speicher der nächste zu bearbeitende Befehl geholt. Dieser Befehl wird bei DECODE durch das Steuerwerk in Schaltinstruktionen aufgelöst, die das Rechenwerk "verstehen" kann. Aus RAM und ROM werden nun bei
FETCH OPERANDS die Operanden geholt, also die Werte, die durch den Befehl verändert werden sollen bzw. die als Parameter verwendet werden, also etwa die beiden Operanden einer Addition. Bei EXECUTE wird die Operation vom Rechenwerk ausgeführt. Bei UPDATE INSTRUCTION POINTER wird der Befehlszähler erhöht, damit der Rechner "weiß", an welcher Stelle des Programms er sich gerade befindet. Das geschieht parallel zum DECODE und FETCH OPERANDS. Beim EXECUTE kann der Befehlszähler wieder verändert werden (Sprungbefehl). Anschließend kann der Zyklus von vorn beginnen und der nächste Schritt des Programms ausgeführt werden. Ist das Programm zu Ende, bleibt der Rechner stehen und beendet seine Arbeit. Doch wie bringt man einen solchen Automaten dazu, all diese Dinge zu tun?

Der Binärcode
Eine Maschine, die aus unzähligen kleinen Schaltungen aufgebaut ist, kann natürlich nicht mit derselben blumigen Sprache arbeiten, wie das ein Mensch tut. Ein digitaler Schalter kennt nur zwei Zustände: Schalter geschlossen, Strom fließt - oder Schalter offen, Strom fließt nicht. Für viele Menschen mag es unwirklich erscheinen, wie man mit einem solchen beschränkten und simplen Wortschatz Daten verarbeiten kann oder gar einen 3D-Shooter auf den Bildschirm zaubern soll. Doch es ist alles lediglich eine Frage der Definition und des Umfangs, wie man diese Sprache nutzt.

Nehmen wir einen Lichtschalter. Auch er kennt nur zwei Zustände: Ein (Licht brennt) oder aus (Licht brenn nicht). Haben wir, die wir gerade aus dem Kino kommen, mit unserem Freund vorher ausgemacht, es soll das Licht m Flur vorher brennen lassen, wenn er noch wach ist und es löschen, wenn er bereits zu Bett gegangen ist, haben wir hier bereits eine primitive Information übertragen und verarbeitet. Mit einer simpler digitalen Informationseinheit von nur einem Bit waren wir also in der Lage, eine komplizierte Entscheidung zu treffen. Welche Informationen hätten wir erst mit mehreren Lampen, also mehreren Bit, übertragen können? Wir hätten zum Beispiel zudem kodieren können, ob wir von der Tankstelle noch eine Flasche Wein mitbringen sollen, ob wir oben oder unten klingeln sollen oder wie lange wir noch aufbleiben sollen. Man muss nur einmal zuvor definieren, welcher Code was heißen soll.

Der Transistor (1)
Doch natürlich befinden sich in einem Prozessor keine Lichtschalter. Hier kommen diese Schalter stark miniaturisiert in Form eines Transistors vor. Der Transistoreffekt wurde 1947 durch William Shockley, John Bardeen und Walter Brattain in den Bell Laboratories von der Firma AT&T entdeckt. Diese drei erhielten 1956 für die Entdeckung des Transistoreffektes den Nobelpreis für Physik. Durch einen kleinen Steuerstrom kann in einem Transistor ein wesentlich größerer Strom gesteuert werden.

Der Transistor wurde auf der Grundlage der Diode entwickelt. Eine Diode besteht aus zwei dotierten Halbleiterschichten (NP- beziehungsweise PN- dotiert) und lässt Strom nur in einer Richtung durchfließen. Ein Transistor ist im Wesentlichen eine Zusammenschaltung aus drei Diodenhälften (NPN beziehungsweise PNP), wobei die obere und untere Schicht aus Kollektor beziehungsweise Emitter und die mittlere Schicht als Basis bezeichnet werden.

Der Transistor (2)
Beim Bipolartransistor steuert ein Strom IB im Basis-Emitter-Kreis einen (stärkeren) Strom IC im Kollektor-Emitter-Kreis. Der Vorteil dieser stark miniaturisierten Schalter: Es passen Tausende, ja Millionen davon auf einen einzigen Chip. Die Kombination aus diesen Millionen  von Transistoren erlaubt die Darstellung von unzähligen Funktionen, die wiederum in ihrer Kombination den Befehlssatz eines Prozessors ausmachen.

Das Dual-System
Das Dual-System beschreibt ein Stellenwertsystem zur Darstellung von Zahlen aus der Basis 2. Warum ausgerechnet die 2? Wenn eine Informationseinheit nur 2 Zustände (Ein/Aus) annehmen kann, dann kann man Zahlen natürlich nicht im Dezimalsystem (Basis 10) darstellen, wie wir es normalerweise tun. Man muss sich auf das Dual-System beschränken. Während bei unserem Dezimalsystem zehn Zahlen (1 und 9) möglich sind, ehe wir eine zusätzliche Stelle benötigen (10), sind es bei Dualzahlen nur zwei. Zählen würden wir demnach so: 0, 1, 10, 11, 100,101,110 usw., wobei die Dualzahl 10 im Dezimalsystem der 3 entspricht, die duale 11 der 4 usw. Es ändert sich also nichts an den Zahlen, sondern nur an der Schreibweise.

Mit diesen Dualzahlen im binären Code (daher auch Binärzahlen oder Zahlen im Binärcode genannt) kann eine aus Schaltungen bestehende Maschine nun hervorragend arbeiten. Man muss nur festlegen, dass eine 1 dem Zustand "Strom fließt" bzw. "Spannung angelegt" entspricht, eine 0 dem Zustand "Strom fließt nicht" bzw. "keine Spannung angelegt" entspricht. Der Übersicht halber benötigen wir noch eine feste Größe, um bei einer Zahlenfolge von 110 erkennen zu können, ob wir eine 1 und eine 110 (also eine dezimale 1 undd eine 6) darstellen wollen, oder eine 1110 (entspricht dezimaler Zahl 14). Dazu muss man sich zuvor auf die Bitlänge einigen.

Der kleinste Datentyp, den ein PC kennt, ist das Byte, eine Zahl bestehend aus 8 Bit, also 8 Stellen. Das Byte wird rechtsbündig geschrieben und linksseitig mit Nullen aufgefüllt. Beim Zählen 1,2,3, ... würde man also so schreiben: 00000001, 00000010, 00000011,... .

Der Halbadder (1)
Bisher sind wir in der Lage, Informationen und sogar Zahlen binär zu speichern. Einen Rechner jedoch haben wir noch nicht, denn noch können wr nichts verarbeiten. Das Rechenwerk der Von-Neumann-Architektur fehlt. Dazu benötigen wir eine ALU (Arithmetic Logic Unit) , also eine Einheit, die rechnen und "entscheiden" kann. Dazu sind viele Teilschaltungen wie Vergleicher, Halbadder, Volladder usw. notwendig.

Doch um zu zeigen, wie man überhaupt binär rechnen kann, möchten ich die Schaltung eines Halbadders am Beispiel erklären. Ein Halbadder ist eine elektrische Schaltung, die aus 2 Ein-Bit-Ausgangswerten A und B ein Zwei-Bit-Ergebnis produziert, das aus einer Summe S und einem Carry-Bit ("Übertrag") C besteht. Die benötigten Funktionen sind: XOR ("exklusives ODER") sowie AND ("UND"). Ein XOR liefert immer dann eine 1 als Ergebnis, wenn A und B mit 1 belegt sind. Ansonsten liefert UND eine 0.

Der Halbadder (2)
Neben diesen beiden Funktionen gibt es noch das ODER. Hier wird eine 1 als Ergebnis geschrieben, wenn eine der beiden Ausgangszahlen an der Stelle eine 1 hatte. Bei einer NICHT-Funktion dagegen wird die Zahl einfach invertiert, also überall, wo eine 0 stand, wird eine 1 geschrieben und umgekehrt. Wir haben 2 Zahlen A und B, eine Summe S und einen Übertrag C. Beschränken wir uns auf die Berechnung der Summe S. Die Formel für die Schaltung des Halbadders lautet: S = A XOR B. Steht z. B. in Zelle A eine 1 und in B eine 0 als Zahl, kann man mit logischen Operatoren wie folgt rechnen. Verknüpfen wir "A XOR B", so erhalten wir als Ergebnis eine 1. Das XOR bedeutet also, da in dem Moment eine 1 als Ergebnis geliefert wird, wenn einer der beiden Operanden eine 1 als Wert hatte.

 A 
 B 
 C  S 
0000
010 1 
1001
1110

Verknüpfen wir dagegen mit UND, so erhalten wir nur dann eine 1, wenn beide Zahlen dien 1 als Wert trugen. Der Operator NICHT dagegen ist nur auf einen Operanten anwendbar. Er macht nichts anderes als seine Zahl zu invertieren. War zuvor eine 1 an der Stelle, erhalten wir als Ergebnis eine 0 und umgekehrt. Aus nichts anderem als diesen logischen Verknüpfungen ist ein Halbadder aufgebaut.

Der Halbadder (3)
Natürlich funktioniert das Ganze auch mit komplizierteren Zahlen. "NICHT 0111" würde z. B. als Ergebnis "1000" liefern. Eine ODER-Verknüpfung zwischen "1001" und "1010" würde als Ergebnis "1011" ausspucken, während ein XOR als Ergebnis "0011" hätte. Dieselben beiden Zahlen mit UND statt mit ODER verknüpft, hätte "1000" als Ergebnis. Wie man sieht kann man mit simplen logischen Funktionen binäre Zahlen manipulieren oder mathematische Berechnungen durchführen. Mit der richtigen Abfolge an Schritten sind wir demnach mit unserem simplen Prozessor bereits in der Lage, Zahlen zu addieren. Ein paar dieser Einheiten miteinander kombiniert und wir können bereits subtrahieren, multiplizieren oder Ganzzahl dividieren. Die erste Aufgabe, die ein Computer in seiner Geschichte hatte, nämlich Berechnungen durchzuführen, können wir demnach bereits nachvollziehen. Der Grundstein ist also gelegt.

Register
In dem oberen Teil habe ich die Rechner-Architektur nach von Neumann bereits ausgiebig beleuchtet. Die vier Arbeitseinheiten Eingabe-, Steuer-, Rechen- und Ausgabewerk sind ebenso zentraler Bestandteil der Architektur wie der Speicher. Auch alle heute gebaute Prozessoren basieren noch immer auf dem Prinzip von Neumanns. Doch die Entwicklung ist natürlich nicht stehen geblieben. Einer der vielen Flaschenhälse der Von-Neumann-Architektur war das Fehlen von Registern. Register sind schnelle Zwischenspeicher direkt im Kern des Prozessors. Damit stellt der Prozessor letztendlich seine eigentlichen Berechnungen an. So müssen die Daten nicht direkt im quälend langsamen Arbeitsspeicher miteinander verrechnet werden.

Register bestehen aus bistabilen Kippgliedern, auch Flipflops genannt. Ein klassischer x86-PC-Prozessor verfügt über 14 Register, 8 Universalregister (GPR, General Purpose Register) sowie 6 spezialisierte Register, in denen Zahlen zur Berechnung abgelegt werden oder Zeiger auf die Stelle des Programmcodes gesetzt werden können, an der sich der Rechner gerade im Programmablauf befindet. Ein Prozessor kann Operationen auf Länge der Register durchführen. Das bedeutet, dass ein 32-Bit-Prozessor Operationen mit 32-Bit-Zahlen durchführen kann, also mit Zahlen, die 32 Nullen oder Einsen enthalten können. Jeder Operand bei mathematischen Operationen liegt im Bereich von 0 bis 4.294.967.295 (=2^32).

Arbeiten mit Registern
Der normale Anwendungsprogrammierer, der eine Programmierhochsprache wie C++, Visual Basic oder Java beherrscht, kommt normalerweise mit Registern nie in Berührung. Er programmiert mit einigermaßen verständlichen Prozeduren, Funktionen, Klassen und Bibliotheken. Anders sieht die Sache allerdings auf Maschinensprache-Ebene aus. Hier können die Register eines Prozessors unmittelbar beschrieben, ausgelesen und miteinander verarbeitet werden. Im Endeffekt werden natürlich auch die Quellcodes von Hochsprachen in Maschinensprachen übersetzt. Allerdings übernimmt diese Arbeit der Compiler, während der Assembler-Programmierer diese Abstraktion nicht zur Verfügung hat.

Doch egal ob Hochsprachen oder Assembler-Compiler die Aufgabe übernehmen, die vier Register AX,BX, CX sowie DX können in einem 8086-Prozessor, dem Urkeim aller aktuellen PC-Prozessoren, für universelle Operationen verwendet werden. Als Folge dessen, müssen alle Nachfolger dieses Mikroprozessors diese Register besitzen und deren Verwendung identisch handhaben. So verlief auch die Entwicklung bis heute. Allgemein werden diese Prozessoren als x86-Prozessoren bezeichnet.

Werfen wir einen Blick auf ein Mini-Assemblerprogramm, um zu sehen, wie das funktioniert. Zunächst ist es notwendig, die zwei für die Addition benötigten Operanden in Registern abzulegen. Hierzu sei die Anfangsadresse der Operanden im Hauptspeicher abgespeichert im ESI-Register:

- MOV EAX, [ESI]
- ADD ESI, 00000004h
- MOV EBX, [ESI]
- ADD EAX, EBX

Soll das Ergebnis nun noch in den Hauptspeicher zurückgeschrieben werden, ist wiederum ein Arbeitsschritt nötig. Dabei sei der Anfang der Zieladresse im EDI-Register abgespeichert:

- MOV [EDI], EAX

4h ist in diesem Fall hexadezimale Schreibweise von 4 Byte, also von 32 Bit. Die Anfangsadresse wird hier um die Breite eines 32-Bit-Registers im Arbeitsspeicher weitergeschoben.

Rechnen mit größeren Zahlen
Wie bereits beschrieben kann ein 32-Bit-Register maximal eine 32-stellige Binärzahl aufnehmen. Aus diesem Grund kann eine 32-Bit-Zahl nie einen größeren Wert annehmen als 4.294.967.295. Doch natürlich können 32-Bit-Prozessoren auch größere Zahlen verarbeiten, schließlich würde ein moderner Prozessor damit ja schon an der Berechnung der Staatsverschuldung scheitern.

Dazu ist es allerdings nötig, die Berechnung aufzuteilen. Im Arbeitsspeicher steht, nach Intels Little-Endian-Methode, der niederwertige Teil einer Zahl an der kleineren Adresse. Da der Hauptspeicher nach Intels x86-Standard in 8-Bit-Reihen organisiert ist, benötigt eine 32 Bit lange Zahl folglich 4 Speicherzellen. Für das Rechenbeispiel wird eine 64-Bit-Zahl verwendet, die an zwei aufeinanderfolgenden Adressen im Hauptspeicher steht. Als Beispiel-Operation wird der Einfachheit halber wie oben die Addition verwendet. Zunächst ist es auch hier wieder notwendig, die zwei für die Addition benötigten Operanden in Registern abzulegen:

- MOV EAX, [ESI]
- ADD ESI, 00000004h
- MOV EBX, [ESI]
- ADD ESI, 00000004h
- MOV ECX, [ESI]
- ADD ESI, 00000004h
- MOV EDX, [ESI]

Nun liegt also im EAX-Register der niederwertige Teil des ersten 64-Bit-Operanden, im EBX-Register der höherwertige Teil des zweiten 64-Bit-Operanden und im EDX-Register der höherwertige Teil des zweiten 64-Bit-Operanden. Die Addition erfolgt nun in zwei Arbeitsschritten: Zunächst müssen die beiden niederwertigen Teile addiert werden, die geschiet mit:

- ADD EAX, ECX

Das Ergebnis steht nun im EAX-Register. Sollte das Ergebnis dieser Addition größer als 2 hoch 32 sein, so wird das Carry-Bit im Statusregister gesetzt. Im zweiten Arbeitsschritt werden nun die beiden höherwertigen Teile addiert. Da ein eventueller Übertrag aus der Addition der beiden niederwertigen Teile beachtet werden muss, wird anstelle von ADD der Befehl ADC verwendet. Dieser führt eine Addition unter Beachtung des Carry-Flags durch:

- ADC EBX, EDX
 
Das Gesamtergebnis ist nun wieder auf zwei 32-Bit-Register aufgespalten. Im EAX-Register steht der niederwertige Teil des 64-Bit-Ergebnisses, im EBX-Register der höherwertige Teil. Soll das Ergebnis nun noch in den Hauptspeicher zurückgeschrieben werden, sind dieses Mal zwei Arbeitsschritte nötig.

- MOV [EDI], EAX
- ADD EDI, 00000004h
- MOV [EDI], EBX
- ADD EDI, 00000004h

Nun liegt die 64-Bit-Zahl zur weiteren Verwendung wieder an zwei aufeinanderfolgenden Adressen (also acht aufeinanderfolgenden Zellen) im Hauptspeicher.

Adress-Register
Wie man sieht, ist das Bearbeiten größerer Zahlen mit nur 32-Bit breiten Registern ziemlich umständlich. Doch nicht nur aus diesem Grund wünschen sich die Programmierer möglichst breite Register. Ein 32-Bit-Register kann auch nur eine beschränkte Anzahl an Adressen im Arbeitsspeicher aufnehmen; für ein 32-Bit-System bedeutet das: bei 4 GByte RAM ist Schluss. Mehr Speicher kann ein 32-Bit-Prozessor nicht addressieren (zumindest nicht auf herkömmlichen Wege). Die Einführung von 64-Bit-Prozessoren, also von Prozessoren mit 64 Bit breiten Registern, entschärft das Problem, indem hier rein rechnerisch 2 hoch 64 Byte RAM addresiert werden können, also satte 16.777.216 Terabyte. Eine nicht vorstellbar große Menge. Aus diesem Grund haben die aktuellen 64-Bit-x86-Prozessoren wie der Athlon 64 eine freiwillige Selbstbeschränkung: Nur 40 Bit stehen für die Adressierung des Arbeitsspeichers zur Verfügung. Das sind 1 Terabyte RAM, was für die nächsten Jahre erst einmal genügen sollte. Auf die 64-Bit-Modi der aktuellen Prozessoren werde ich an einer anderen Stelle, wo es besser angebracht ist, näher darauf eingehen.

Flipflops
Register sid aus sogenannten Flipflops aufgebaut. Flipflops sind als 1-Bit-Speicher anzusehen. Entwickelt wurde die Flipflop-Schaltung auf der Suche nach Zählschaltungen von den Engländern William Henry Eccles und F. W. Jordan an rückgekoppelten Radioröhren-Verstärkern, ursprünglich erhielten diese die Bezeichnung Eccles-Jordan-Schaltung. Flipflop gibt lautmalerisch das Geräusch wieder, welches die Kippvorgänge in der Schaltung an einem Lautsprecher, der in einem der Ausgänge liegt, hervorrufen.

Ein Flipflop hat 2 Zustände: gesetzt (set) und zurückgesetzt (reset). Um ein Flipflop (RS-Flipflop) zu setzen, muss am Eingang "set" ein Singnal angelegt werden. Danach bleibt das Flipflop im gesetzten Zustand, bs ein weiteres Signal auf den Eingang "reset" gelegt wird. Andere Flipflop-Schaltungen (D-Flipflops) haben andere Eingänge: Um den Zustand des Flipflops zu ändern, muss am Eingang "Takt" (clock) ein Signal angelegt werden. Abhängig vom Signal am Eingang "Daten" (data) wird es dann gesetzt oder zurückgesetzt. Diese Zustände
können jeweils als eine binäre Ziffer interpretiert werden (0 oder 1).

Die FPU (1)
ALU (siehe Teil 1) und Register sind elementare Bauteile eines x86-PCs. Ein weiteres wichtiges Bauteil ist die FPU, die Floating-Point-Processing-Unit. Aus einem modernen PC-Prozessor ist dieses Bauteil nicht mehr wegzudenken. Elementar für eine x86-CPU ist es allerdings nicht. Weder die ersten 8086- und 8088-Prozessoren besaßen eine FPU noch die 80286- und 80486-Prozessoren waren nicht notwendigerweise mit einer FPU ausgerüstet. Bei letzterem Prozessortyp war diese Einheit -falls vorhanden- allerdings bereits direkt im Prozessorkern integriert. Die älteren Rechner dagegen mussten mit einem zusätzlichen, sogenannten mathematischen Koprozessor bestückt werden.

Doch wozu eine FPU? Die normale ALU ist nicht in der Lage, Fließkomma-Berechnungen anzustellen. Die ALU kann nur ganze Zahlen verrechnen und auch nur Grenzzahl-Operationen durchführen. 1 plus 2, 10 minus 8 oder 8 mal 5, das ist kein Problem für die ALU. Auch das Dividieren von Ganzzahlen, also z. B. 10 geteilt durch 2 oder 14 geteilt durch 3 (ist gleich 4, Rest 2) liegt im Bereich des Möglichen. An einer Berechnung wie 125,4845 geteilt durch 17,5 jedoch scheitert die ALU. Dafür ist sie nicht konzipiert worden. Ein Programm, das solche Berechnungen mit einem x86-Prozessor ohne FPU durchführen wollte, musste die Funktion per Software emulieren. Man kann sich vorstellen, wie aufwendig eine solche Funktion als Emulation durchzuführen ist, wie viele Einzelschritte dafür notwendig sind und wie entsprechend langsam das vonstatten geht.

Die FPU (2)
Mit einem mathematischen Koprozessor dagegen konnten solche Berechnungen
"in Hardware" und damit wesentlich schneller durchgeführt werden. Allerdings war damals kaum Bedarf an solchen Berechnungen. Zeitkritische Anwendungen solcher Art gab es praktisch kam im normalen Einsatzgebiet. Selbst die ersten Spiele wurden damals ressourcenschonend mit Ganzzahl-Berechnungen programmiert. Erst die 3D-Spiele machten es notwendig, dass Prozessoren für die Berechnung der Koordinaten im dreidimensionalen Raum auch Fließkomma-Berechnungen in brauchbarer Geschwindigkeit durchführen konnten.

Als Folge davon wurden die FPUs ab Mitte der 90er-Jahre immer mächtiger und schneller. Die meisten FPUs stellen Operationen für die Grundrechenarten, Logarithmus-, Wurzel- und Potenzrechnung und trigonometrische Funktionen zur Verfügung. Als Datentyp kann "float" oder damit verwandte Typen verwendet werden.

Aufbau: Fließkommazahlen
Eine Fließkommazahl besteht grundsätzlich aus drei Elementen: der Mantisse, der Basis und einem Exponenten. Die Zahl 17,5 dargestellt als Fließkommazahl würde man 1,75 mal 10 hoch 1 schreiben. Um eine solche Zahl in ein 32 Bit breites Register zu quetschen, muss man zuerst einmal definieren, welche Stellen wofür verwendet werden sollen. Gemäß Spezifikation IEEE 754 für Flot-Zahlen mit einfacher Genauigkeit wird dabei ein Bit für das Vorzeichen verwendet (eine Fließkomnmazahl kann ja auch negativ sein), 8 Bit für den Exponenten und der Rest für die Nachkommastellen (Mantisse).

Eingebaute Rundungsfehler
Bei der Darstellung und vor allem bei der Weiterverarbeitung von Fließkommazahlen muss man als Programmierer stets im Hinterkopf behalten, dass eine solche Zahl lediglich eine mehr oder minder genaue Rundung der reellen Zahl darstellt. Je geringer dabei die Anzahl der Bits, die für dien Mantisse zu Verfügung stehen, desto ungenauer die Rundung.

Führt man ein paar weiterführende Berechnungen damit durch, im ungünstigsten Fall ein paar Multiplikationen mit großen Zahlen, kann das Ergebnis mathematisch falsch werden, auch wenn die FPU bei der Berechnung keinen Fehler gemacht hat. Ein Beispiel: Nehmen wir die Berechnung 10 geteilt durch 3. Dabei kommt 3,3 Periode heraus. Nehmen wir der Einfachheit halber an, wir könnten lediglich eine Nahkommastelle darstellen, also 3,3. Multipliziert man dieses Ergebnis mit 1.000, kommt man auf 3.300 - was aber falsch ist! Denn in Wirklichkeit ist das Ergebnis 3.333,3 Periode.

Besonders kritisch sind dabei Divisionen. Vor allem, wenn Auslöschung droht, also der Nenner nahe bei Null liegt oder liegen kann, kann eine zu niedrige Genauigkeit extreme Unterschiede bei den Ergebnissen zur Folge haben. Auch aus diesem Grund wünschen sich die Programmierer Float-Datentypen mit möglichst großer Breite und damit hoher Genauigkeit.

CPI - das Grundproblem
Im obrigen Teil habe ich vielleicht durch die vereinfachte Darstellung den Eindruck erweckt, dass man mit ein paar Transistoren und Flipflops einen Prozessor bauen kann, der jede belibige Berechnung durchführen kann - und das auch noch in der Geschwindigkeit, die wir heute gewohnt sind. Dem ist natürlich nicht so. Selbst der allererste x86-Prozessor, Intels 8086, bestand bereits aus 29.000 Transistoren. Verglichen mit den heute üblichen Transistorenzahlen im zwei- bis dreistelligen Millionenbereich ist das zwar nicht viel, aber es zeigt, das der Aufwand, der betrieben werden muss, um einen Prozessor mit den benötigten Funktionen auszustatten, immens ist. Genauso immens ist auch der Asufwand, den ein Prozessor betreiben muss, um eine simple Instruktion in der Praxis umzusetzen.

Um eine einzige Funktion auszuführen, benötigte das Urmodell der heutigen Intel-Core- und AMD-Athlon-64-Prozessoren, der Intel 8086, unzählige Takte abhängig von der Art der Instruktion. Der Fachbegriff dafür lautet CPI (Clockcycles per Instruction). Dieser Wert beschreibt die Anzahl der Takte, die benötigt werden, um eine Instruktion auszuführen. Je höher der CPI- Wert eines Prozessors, desto ineffizienter ist sein Design. Schon bei den ersten Prozessoren wurden die Schaltungen in den CPUs zwar immer weiter optimiert, aber wir werden gleich sehen, dass ein herkömmliches CU-Design stets einen CPI-Wert von deutlich größer als 1 haben muss.

Funktionsablauf
Wie bereits im obrigen Teil erwähnt, besteht der standig wiederkehrende Funktionsablauf in einem Prozessorim Wesentlichen aus den Stufen IF (instruction Fetch), ID Instruction Decoding), EX (Execution), WB (Write Black), wobei sich der Ablaufplan im Bereich EX je nach Darstellung um einen MEM- oder Fetch-Operands-Zyklus erweitern lässt - schließlich müssen nicht nur die Instruktionen aus dem Speicher geladen werden, sondern auch noch die verarbeitenden Daten. Gehen wir also in meinem Beispiel einmal von der Reihenfolge IF, ID, EX, MEM und WB aus. Das sind fünf verschiedene Vorgänge, die ein Prozessor nacheinanderausführen muss, um einen einzigen Befehl zu verarbeiten.

Selbst wenn die CPU-Entwickler ganze Arbeit geleistet haben und jede dieser Sektionen in einem einzigen Takt abgearbeitet sind, wird schnell deutlich, dass mein Beispielprozessor einen CPI-Wert von fünf nicht wird unterschreiten können.













Prozessor-Glossar

ALU
Arithmetisch-logische Einheit, eine für logische und Ganzzahl-Berechnungen zuständige Einheit in der CPU.

Assembler
Ein spezieller Compiler, der ein in maschinennaher Sprache geschriebenes Programm in ausführbaren Code übersetzt.

AX
16 Bit breites, prozessorinternes Register, das für Berechnungen genutzt wurde.
Später (mit 32 Bit) EAX genannt (E=Enhanced)

Carry-Bit
Wörtlich: Übertragsbit. Zeigt an, dass bei der Berechnung ein Übertrag entstanden ist, mit dem weitergerechnet werden muss.

CPU
Central Processing Unit, ist der zentrale Prozessor eines Computers, der alle anderen Bestandteile steuert.

Nibble

Einheit bestehend aus vier Bits, manchmal auch Quadrupel genannt; 2 Nipples ergeben 1 Byte (= 8 Bits).

Bit
Die kleinste Einheit einer Information; kennt nur zwei Zustände: 0 oder 1. 8 Bit ergeben
1 Byte.

Transistor
Bauelement eines Prozessors. Eine Art Mikroschalter, der Strom wahlweise leitet oder nicht.

x86-Prozessor
Ein Prozessor, der zum Intel 8086 kompatibel ist. Intels 8086 erschien 1978 und wurde bei den ersten IBM-PCs eingesetzt.