CPU-Grundlagen: Multithreading

11.11.2004 von PROF. DR. CHRISTIAN SIEMERS 
Eine weitere Leistungssteigerung bei Prozessoren ist nur durch die parallele Abarbeitung weitgehend unabhängiger Threads möglich. Neben Intels Hyper-Threading stehen weitere viel versprechende Ansätze zur Wahl.

Allgemein teilt man die möglichen Parallelitätsebenen bei der Programmausführung in vier verschiedene Klassen ein:

RISC und superskalare Prozessoren setzen dabei aber nur die Hardware- und die Instruktions-Level-Parallelität um. Die "Enden" dieser Parallelitätsskala sind wohl bekannt, Hardware- und Prozessparallelität kommen seit Jahren zum Einsatz. Gleichwohl ist der Ansatz der Prozessparallelität, also eines MIMD-Rechners gemäß Flynn'scher Klassifikation, in seiner Güte sehr abhängig von der Software. Die Instruktions-Level-Parallelität nutzen superskalare und VLIW-Prozessoren ebenfalls schon sehr intensiv. Daher bleibt nur noch die Threadlevel-Parallelität als Forschungs- und Entwicklungsthema. Es stellt sich zunächst die Frage, was ein Thread eigentlich ist:

Diese Definition ist in gewissem Sinn "weich", sie lässt eine Menge Interpretationsraum. Threads können durch den Software-Entwickler definiert sein (Entwicklung von Multithreaded-Programmen), ebenso könnte man kleinere Einheiten, bis hin zu den Basisblöcken (siehe Superskalare Prozessorarchitekturen), als Thread bezeichnen - dann jedoch mit anderem Kontext und Eigenschaften.

Fein- und grobkörnige Paralleleität

Dementsprechend verfolgt dieser Artikel zwei Ansätze: Feinkörnige und grobkörnige Architekturen zur Ausnutzung des Multithreading. Was die Körnigkeit betrifft, ist eine scharfe Grenze schwer zu ziehen. Dennoch gibt es als ein klares Unterscheidungskriterium die Datenabhängigkeiten.

Die folgenden Abschnitte stellen zwei Ansätze für feinkörnige Parallelität im Multithreading vor (Multiskalarer Prozessor, Trace-Prozessor), gefolgt von dem weit verbreiteten Ansatz des Simultaneous Multithreading (SMT). Im dritten Abschnitt dient dann Intels Pentium 4 als konkretes SMT-Beispiel.

Feinkörnige Parallelität

Der Ansatz zu einer Ausführung von parallelen Threads mit Feinkörnigkeit besteht in der Fokussierung auf Instruktionsblöcke. Hier liegt es nahe, die Basisblöcke (siehe Superskalare Prozessorarchitekturen) und die schon beschriebenen Hyperblöcke (siehe Compiler-Techniken für superskalare Prozessoren) zu Grunde zu legen. Diese Blöcke beinhalten einen ungestörten sequenziellen Fluss von Instruktionen und eignen sich ideal für die superskalare Ausführung. Teil a) des Bilds zeigt den Kontrollflussgraphen (CFG, Control Flow Graph) eines Beispielprogramms: Die Instruktionsblöcke - auch als Microthreads bezeichnet - stellen die Knoten, die Übergänge zu anderen Blöcken die Kanten dar. Die Darstellung als CFG beinhaltet zunächst nur die statische Repräsentation des Programms, Aussagen über den tatsächlichen Programmverlauf sind nicht enthalten.

Den dynamischen Kontrollfluss stellt Teil a) des Bilds ebenfalls dar. Das Programm durchläuft je nach Kontext verschiedene Knoten. Die Auswertung dieses aktuellen Programmverlaufs behandeln die folgenden Ansätze zum Multithreading jeweils ganz unterschiedlich.

Multiskalare Prozessoren ordnen verschiedenen Knoten des CFG, also Instruktionsblöcken, verschiedene Prozessorelemente (PE) der CPU zu. Diese bewirkt, dass der Prozessor mehr auf Instruktionsblockbasis als auf Instruktionsbasis arbeitet. Teil b) des Bilds zeigt eine derartige Abbildung der Microthreads auf die Prozessorelemente. Die multiskalare Architektur besticht dabei durch einen einfachen Ansatz: Im Gegensatz zur superskalaren CPU verteilt sie die Ressourcen auf mehrere, eher als RISC-Architektur ausgeführte und weit gehend eigenständige Prozessoren (PE genannt). Jeder dieser PEs bekommt einen Microthread zur Ausführung. Der Name multiskalar rührt von dem Ansatz her, dass viele skalare Ausführungseinheiten nebeneinander arbeiten.

Multiskalarer Prozessor

Der bestechend einfache Ansatz mit PEs wird aber durch die dynamische Verwaltung und vor allem die Datenabhängigkeiten zwischen verschiedenen Microthreads komplex. Im Unterschied zu einem Multiprozessoransatz (meist als CMP, Chip-integrated Multiprocessing bezeichnet) bestimmt nicht der Compiler oder der Assembler-Programmierer, welcher Prozessor welchen Teil berechnet.

Diese Aufgabe übernimmt die CPU dynamisch zur Laufzeit. Das bedeutet, dass sie eine dynamische Zuordnung von Microthreads zu PEs durchführen muss, meist ohne zusätzliche Informationen vom Compiler. Die zweite Schwierigkeit besteht in den möglichen Datenabhängigkeiten. Wie soll der multiskalare Prozessor solche Datenabhängigkeiten auflösen oder auch nur erkennen?

Das Bild gibt Aufschluss über den Aufbau und damit auch die Arbeitsweise des multiskalaren Ansatzes. Die Processing Elements PE (Anzahl n) sind in einer Art einfacher Verkettung zu einem Ring miteinander verbunden. Zunächst ist es die Aufgabe des Sequencers, die Microthreads an die PEs zu verteilen. Zur Vorhersage des Kontrollflusses ist auch die Branch Prediction in dieser Einheit enthalten. Die einzelnen PEs sind dann für den Fetch ihrer Instruktionen selbst verantwortlich und besitzen auch einen eigenen Instruktions-Cache.

Jedes PE enthält auch ein vollständiges Register File (mit je einem physikalischen Register pro logischem Register). Beim Instruktionsblock fetch detektiert der multiskalare Prozessor, welche logischen Register potenziell beschrieben werden. Er informiert alle im Ring nachfolgenden PEs über diesen Schreibzugriff und löst eventuell bestehende RAW-Hazards durch Wartezyklen auf. Die Kommunikation über die betroffenen Register erfolgt typisch über Bitmasken.

Die Auflösung von Speicherzugriffen ist schwieriger. Dafür ist ein Address Resolution Buffer (ARB) zuständig, den die PEs über einen gemeinsamen Interconnect ansprechen. Der ARB löst Schreib-/Lesezugriffe auf gemeinsame Adressen auf und vermeidet so Zugriffskonflikte. Letztendlich ist aber der Speicherzugriff einer der entscheidenden Engpässe der multiskalaren Architektur. Typisch findet man daher auch noch zwei Formen der Spekulation:

Trace-Prozessor

Der Trace-Prozessor ist ein anderer Ansatz, zu einer feinkörnigen, parallelen Thread-Ausführung zu kommen. Er zeichnet bei einem ersten Durchlauf den Instruktionsfluss in einem speziellen Instruktions-Cache, dem Tracecache, auf. Dabei durchläuft der Prozessor auch Instruktionsblockgrenzen, so dass der Tracecache mehrere Kontrollflussspekulationen implizit speichert.

Die konkrete Implementierung eines Tracecache kann in Form der Startadresse und der Branch-Vorhersagen erfolgen. Dieser Ansatz erfordert nur einen minimalen Speicheraufwand, hat aber den Nachteil, dass der Instruktions-Fetch trotzdem aus dem Instruktions-Cache oder dem Hauptspeicher erfolgen muss. Die bessere Methode besteht darin, die komplette Folge der Instruktionen im Tracecache zu speichern. In diesem Fall kann das Laden des kompletten Codes in einem Takt erfolgen, allerdings benötigt die CPU dafür einen deutlich größeren Tracecache.

Das Bild zeigt eine mögliche Implementierung eines Trace-Prozessors. Im Prinzip ist er so aufgebaut wie der multiskalare Prozessor (mit dem Unterschied in der Fetch-Einheit), allerdings nutzt er in dieser Darstellung eine andere Art der Kommunikation. Das Processing Element 0 führt den aktuellen Trace aus, während die anderen die zukünftigen Traces unter spekulativen Bedingungen bearbeiten. Die globalen Register dienen dabei dem Datenaustausch. Das bedeutet aber, dass das Programm für den Trace-Prozessor kompiliert sein muss, um die Datenabhängigkeiten auf Registerebene zu kennen.

Grobkörnige Parallelität

Im Allgemeinen ist die grobkörnige Parallelität beliebter, weil sie für weniger Datenabhängigkeiten sorgt. Im Extremfall - Grobkörnigkeit auf Prozessebene - ist eine Unabhängigkeit sogar garantiert, bedingt durch die Definition von Prozessen und deren exklusiven Speicherbereichen.

Unterhalb der Prozessebene, also auf Thread-Ebene, gilt die Annahme aber ebenfalls weit gehend. Einer der wesentlichen Anlässe, sich mit der quasi gleichzeitigen Ausführung von Threads in einem Prozessor überhaupt zu befassen, sind die Speicherlatenzzeiten. Insbesondere bei Mehrprozessorsystemen mit physikalisch verteiltem, logisch aber globalem Speicher, sind diese Zeiten teilweise erheblich. Folgende Messwerte gelten für ein System mit vier Alpha-21164-Prozessoren bei 300 MHz Taktfrequenz aus dem Jahr 1998:

In derartigen Systemen (die Zeiten erhöhen sich für Systeme mit mehr Prozessoren noch wesentlich) muss man erwarten, dass sie einen wesentlichen Anteil der Rechenzeit für das Warten auf Datenwerte verwenden. Moderne Speicher- und I/O-Designs wie etwa bei AMDs Opteron mit HyperTransport sollen zwar selbst bei Achtfach-Systemen maximale Latenzzeiten von unter 160 ns bieten. In Relation zur Taktrate von beispielsweise zwei GHz entspricht dies aber immerhin 320 Takten.

Letztendlich liegt die Ursache für die starke Abhängigkeit vom Speicherzugriff darin, dass das Konzept der Von-Neumann-Maschine zwei Zustände berücksichtigen muss: den Prozessorzustand, bestehend aus dem Aktivitätsregister (Program Counter) und dem Registerkontext, sowie den Speicherzustand. Diese Zustände muss die Von-Neumann-Maschine für jede Instruktion so abstimmen, dass die nächste Instruktion beginnen kann - und dies bedeutet häufig Warten. Diese Variante der Mikroprozessoren, die aktuell fast ausschließlich verwendet wird, bezeichnet man als Singlethreaded.

Im Gegensatz dazu kennzeichnet eine Multithreaded-Architektur, dass sie zwar nur einen Prozessorzustand kennt, dieser aber aus einem Satz von Aktivitätsregistern und einem Satz von Registerkontexten besteht. Hieraus ergeben sich andere Möglichkeiten im Fortschreiten der Programmausführung.

Im Allgemeinen hält man eine weitere Leistungssteigerung durch die klassische superskalare Architektur für sehr begrenzt. Der reale Durchsatz superskalarer CPUs liegt bei 4- bis 8facher Superskalarität bei einem Wert von nur 1,5 Instruktionen pro Takt (SPEC92-Benchmarks: 0,96 ... 1,77 beim MPS620 PowerPC). Die Nutzung der grobkörnigen Parallelität in Prozessoren bietet hier die Chance zu erheblichen Steigerungen.

Um die Rechenleistung zu steigern, schalten Multithreaded-Architekturen bei Blockierung eines Threads etwa durch einen Speicherzugriff einfach auf einen anderen Thread um. Dieser schnelle Kontext-Switch ist deshalb möglich, weil mehrere Prozessorzustände parallel vorhanden sind. Bei einer Singlethreaded-Architektur wäre der Kontext-Switch mit erheblichem Aufwand verbunden.

Ansätze zu Multithreaded-Architekturen

Durch die unterschiedlichen Methoden zur Implementierung des Kontext-Switches lassen sich drei Techniken für Multithreaded-Prozessoren unterscheiden:

Cycle-by-Cycle-Interleaving-Technik: Jeder Prozessortakt speist einen Befehl eines anderen Kontrollfadens in die Prozessor-Pipeline ein. Dieses Verfahren besitzt den Nachteil einer geringen Leistung, falls nur wenige Kontrollfäden (Threads) als Last zur Verfügung stehen. Im Regelfall muss erst der Befehl eines Kontrollfadens die Pipeline verlassen, bevor der Prozessor die nächste Instruktion dieses Kontrollfadens laden kann. Diese einfache Form des Threadschedulings im Prozessor hat also pro Faden nicht die Eigenschaften des Befehls-Pipelinings einer der RISC-CPUs.

Andererseits kann gerade in diesem Fall die Befehls-Pipeline sehr einfach ausfallen, da sie keine Daten- oder Kontrollflussabhängigkeiten detektieren muss: Jeder Befehl eines Threads verlässt die Pipeline komplett, bevor der nächste Befehl startet. Auch der Kontext-Switch besitzt keinen Overhead, da er jedes Mal stattfindet. Dadurch stellt er keinen Ausnahmefall wie bei der Block-Interleaving-Technik dar.

Block-Interleaving-Technik: Die CPU führt die Befehle eines einzelnen Kontrollfadens so lange aus, bis ein Ereignis eintritt, das zu Wartezeiten führt. In diesem Fall erfolgt der Kontext-Switch. Wartezeiten entstehen beispielsweise durch eine fehlende Synchronisation, einen fehlgeschlagenen Cache-Zugriff oder auch durch direkte Load-/Store-Zugriffe.

Der Nachteil dieser Technik ist, dass die Pipeline solche Wartezeiten meist erst spät erkennt. Dies führt zu einem hohen Wechselaufwand von mehreren Takten. Außerdem treten eine fehlende Synchronisation, fehlgeschlagene Cache-Zugriffe und Load-/Store-Befehle sehr häufig auf.

Simultaneous-Multithreading-Technik: Mehrere Befehlspuffer bestücken die Ausführungseinheiten eines superskalaren Prozessors simultan. Jedem Kontrollfaden sind ein eigener Registersatz und ein eigener Befehlspuffer zugeordnet. Diese Technik kombiniert also Superskalartechnik mit ihrer breiten Zuordnungsbandbreite und die Multithreading-Technik.

Der Prozessor nutzt dabei die Ausführungsparallelität in deutlich vergrößertem Maß aus, da mehrere Threads mit voneinander unabhängigen Befehlsströmen einen Input für die Funktionseinheiten liefern. Dadurch sind die Abhängigkeiten sehr gering.

Vergleich der Interleaving-basierten Ansätze

Das Bild zeigt die Cycle-by-Cycle-Interleaving-Technik im Vergleich zur RISC-CPU. Die Basis-CPU soll hierbei eine Pipeline-Länge von vier Stufen besitzen. Die Ausführung eines Befehls dauert in der Regel einen Takt, die Ausnahmen entstehen durch Daten- oder Kontrollfluss-Hazards sowie Speicherlatenzzeiten (die auch zu den Daten-Hazards gezählt werden könnten).

Soweit dies möglich ist, führt die CPU in Cycle-by-Cycle-Interleaving-Technik in jedem Takt einen Kontext-Switch durch. Die Anzahl der ausführbaren Threads ist mit drei in dem Beispiel b) zu klein, um die vierstufige Pipeline komplett zu füllen. Aus diesem Grund kommt es zu Pipelinestalls.

Die wesentliche Quelle für Pipelinestalls können jedoch auch Speicherlatenzzeiten sein. Wie bereits dargestellt, sind Wartezyklen von deutlich mehr als vier Takten vor allem bei hochgetakteten CPUs üblich. Selbst L2- und L3-Caches bringen hier keine generelle Verbesserung. Das heißt, dass bei längeren Wartezeiten sehr schnell ein Pipelinestall mit einer erheblichen Anzahl von Wartetakten eintritt.

Obiges Bild stellt den Fall der skalaren (RISC-)CPU mit Block-Interleaving-Technik zum Multithreading dar. Diese Variante schaltet im Kontext um, wenn bestimmte Bedingungen (etwa Load-/Store-Befehle) auftauchen. Das Umschalten bedeutet, dass die CPU diese Bedingung detektieren muss. Dies ist in der Regel in der Decode-Phase, aber auch in der Fetch-Phase möglich. Hieraus entstehen aber meist Wartezyklen. Der wesentliche Vorteil dieser Variante besteht darin, dass sie zum Füllen der Pipeline deutlich weniger Threads benötigt, selbst bei Load-/Store-Zugriffen auf den Speicher. Hierdurch kann sie auch längere Wartephasen ausgleichen und bei entsprechender Hardware sogar mehrere Load-/Store-Zugriffe mehrerer Threads abfangen. Diese Speicherzugriffe erfolgen dann in Reihenfolge ihres Auftretens. All diesen skalaren Varianten ist gemeinsam, dass Datenabhängigkeiten im Speicher (die Register sind als Thread-lokaler Kontext exklusiv vorhanden) nicht auftreten können.

Die Cycle-by-Cycle- und die Block-Interleaving-Technik lassen sich natürlich auch auf superskalare und VLIW-Architekturen anwenden. Die Unterschiede zwischen diesen beiden Varianten sind in diesem Zusammenhang nicht relevant, so dass das Bild nur die VLIW-Variante zeigt (N: No Operation, diese Operation füllt leere Slots in der VLIW-Architektur und entfällt bei der superskalaren Variante). Entscheidend für diese Form des Multithreadings ist, dass sie die Ausführungs-Slots in der Vertikalen ausfüllt, nicht jedoch in der Horizontalen. Dies wird erst im Simultaneous Multithreading erreicht.

Simultaneous Multithreading

Der Ansatz zum Simultaneous Multithreading (SMT) entstammt nicht (nur) dem Bestreben, Latenzzeiten durch die Ausführung anderer Threads zu überbrücken. Vielmehr soll es die Einheiten einer superskalaren oder VLIW-Architektur besser ausnutzen. Hierzu bringt SMT mehrere Threads nicht nur scheinbar, sondern tatsächlich simultan zur Ausführung.

Zur simultanen Ausführung mehrerer Threads sind grundsätzlich zwei Ansätze denkbar: Das (wirkliche) Simultaneous Multithreading (SMT) und das Chip-integrated Multiprocessing (CMP). CMP integriert mehrere Prozessoren komplett auf einem Chip und stattet sie mit einem gemeinsamen Speicher-Interface aus. Häufig übernimmt einer der Prozessoren die Aufgabe, den Prozessoreinheiten die Threads zuzuteilen.

Der CMP-Ansatz ist vergleichsweise einfach zu realisieren, da die Prozessoren voneinander unabhängig agieren und nur über das Speicher-Interface gekoppelt sind. Nachteilig wirkt sich natürlich aus, dass Chip-integrated Multiprocessing die Slots horizontal wiederum nicht auffüllt.

Der SMT-Ansatz zeichnet sich dadurch aus, dass gegenüber dem Thread-Strom scheinbar nur eine CPU ausgeführt ist. Eine geeignete Steuerung lastet diese mit ausführbaren Instruktionen mehrerer Threads möglichst komplett aus.

Dies bedeutet konkret für den Aufbau einer SMT-CPU:

Konkrete Ausführung einer SMT-fähigen CPU

Das Beispiel der SMT-fähigen Implementierung eines vereinfachten Power-PC MPS604soll den Aufbau eines derartigen Prozessors näher zeigen. Der Aufbau enthält vier grundlegende Elemente:

Die Einheiten sind untereinander verbunden, wobei die Anzahl der Verbindung durchaus skalierbar sein kann. Die Anzahl der Steuer-Pipeline-Puffer und der Register Files stimmen miteinander überein, um entsprechende Threads vollständig unterstützen zu können. Die Anzahl der Function Units kann hiervon jedoch abweichen, sie bewirkt den maximal möglichen Instruktionsparallelismus.

Die einzelnen Einheiten benötigen gegenüber einer Singlethreaded-CPU zum Teil besondere Ausführungsformen. So sollte der Befehlspuffer nicht blockierend sein, trotz eines Cache-Miss sollte die Befehlsladeeinheit weitere Instruktionen für einen anderen Kontrollfaden laden können. Diese Konstruktion entkoppelt Befehlsladeeinheit und aktuellen Speicherzugriff voneinander.

Die singuläre Ausführung der Dispatch Unit (Zuordnung) und der Completion Unit (Reordering & Commit Unit, Vervollständigung) ergibt sich durch die gemeinsame Aufgabe für alle Befehlsfäden: Der Dispatcher hat eine Querschnittsfunktion über alle aktiven Threads, die Completion Unit kann mehrere Befehle gleichzeitig vollenden und ist damit vom Befehlsfaden unabhängig.

Die SMT-fähige CPU, auch als Karlsruhe-Prozessor bekannt, ist in erheblichem Maß skalierbar ausgeführt. Sie enthält einen Activation Frame Cache, der Daten aus gerade nicht in Ausführung befindlichen Threads speichert. Dies ermöglicht ein Umschalten ohne signifikanten Overhead und somit ein Multithreading über die Anzahl der Registersätze hinaus.

Pentium 4 mit Hyper-Threading

Mit der für 3.06 GHz ausgelegten Version des Pentium 4 hat Intel eine Variante der bis dahin superskalaren IA32 eingeführt, die SMT-fähig ist. Intel bezeichnet diese Variante als Hyper-Threading.

Die Hyper-Threading-Architektur sieht eine 2-Thread-Struktur vor, die µOps aus zwei verschiedenen Threads abarbeitet. Im Rahmen der 20-stufigen Pipeline (zur Ausführung der µOps) sowie der vorgelagerten Fetch-, Decode- und Übersetzungsphasen ergibt sich dann schematisch eine Ausführungsstruktur wie im Bild gezeigt. Die wesentlichen Einheiten, die hierbei doppelt ausgelegt sind, bestehen aus den µOp-Queues, den Registersätzen (einschließlich Renaming), den Queues vor dem Scheduling, dem Store Buffer und dem Reorder Buffer. Der Rest wird gemeinsam genutzt. Dies führt zu einem Mehrbedarf von nur fünf Prozent Siliziumfläche, wie das Die-Bild deutlich zeigt.

Probleme bei der Ausführung zweier Threads und eine Performance-Einbuße gibt es in der Praxis dann, wenn sich Zugriffe auf den Speicher gegenseitig behindern. Dies tritt recht häufig auf, da der Prozessor die virtuellen Adressen für Einträge im L1-Daten-Cache nur mit 20 Bits speichert, also jedes MByte eine Wiederholung vorkommt. Da vor allem beim Stack sehr oft die Speicherbereiche der beiden Threads um exakt ein Vielfaches von einem MByte auseinander liegen, verdrängen sich die Thread-Zugriffe gegenseitig aus dem Cache. Mit der Anfang 2004 erschienenen Neuauflage des Pentium 4 in einem 90-nm-Design hat Intel dieses Problem jedoch behoben.

Ansonsten ergibt sich aus der Zweiteilung mit geringem Overhead eine signifikante Performance-Steigerung. Sie zeigt die Beschleunigung gegenüber einem einzigen Prozessor durch Hyper-Threading (SMT) und durch Symmetric Multiprocessing (SMP) mit zwei gleichen Prozessoren.

Ausblick

Die Multithreaded-CPU ist ein viel versprechender, weiter führender Ansatz, insbesondere in der Kombination mit einer superskalaren Architektur. Er sorgt bei mehreren Threads dafür, dass die CPU ihre Ressourcen besser ausnutzt. Dies ist vor allem für zukünftige CPUs mit immer mehr Ressourcen entscheidend. Allerdings steigert Multithreading nicht die Performance des einzelnen Threads, wohl aber des Gesamtsystems.

Zurzeit sind zwei Trends zu beobachten: CMP und SMT. CMP gilt dabei als deutlich einfacher in der Implementierung, da die Kontrollmechanismen zur Zuordnung von Instruktionen mehrerer Threads in eine Ausführungs-Pipeline fehlen: Mehrere Prozessoren besitzen eben mehrere Ausführungs-Pipelines. Der SMT-Ansatz hingegen verspricht eine bessere Ausnutzung der vorhandenen Ressourcen. Derzeit zeigt die Tendenz der Hersteller in Richtung SMT.

Der Weg des SMT-Ansatzes in industrielle Produkte - mit dem Intel Pentium 4 3.06 GHz im Jahre 2002 erstmalig beschritten - ist schon deshalb so schwierig, weil die Hersteller die Architektur auch in schnell ausführbare Hardware umsetzen müssen. Da die Hauptverzögerungen in einer VLSI-Schaltung im Strukturbereich kleiner 130 nm nicht mehr von den Transistoren, sondern von den Leitungen stammen, ist besonders ein sorgfältiges Layout wichtig.

Diesen Artikel und eine ganze Reihe weiterer Grundlagenthemen zu Prozessoren finden Sie auch in unserem tecCHANNEL-Compact "Prozessor-Technologie". Die Ausgabe können Sie in unserem Online-Shop versandkostenfrei bestellen. Ausführliche Infos zum Inhalt des tecCHANNEL-Compact "Prozessor-Technologie" finden Sie hier. (ala)