Gut, heute werde ich,
also im Prinzip habe ich jetzt noch drei Kapitel auf der Agenda,
hört sie jetzt natürlich also relativ gewaltig an.
Ich werde aber dann auch relativ vergleichsweise schnell rübergehen.
Also heute wollte ich noch mal was zum Pipelining vorstellen.
Das wird auch noch einigermaßen ausführlich werden. Dann am Freitag
darf er dich noch ein bisschen was zu Parallelarchitekturen sagen,
wobei da geht es also auch nur so um ein paar Grundprinzipien,
so ein bisschen Grundverständnis,
also quasi auch noch als Ergänzung zu dem Das ist eine Parallelberechnung,
die man im Prinzip bei Pipelining dann schon implizit macht.
Und nächste Woche werde ich dann also von diesem Kapitel Speicher-Hierarchie,
was dann quasi das Letzte ist, nur grob,
ein paar Grundideen ansprechen, hauptsächlich Motivation, wofür man das braucht,
ein bisschen auf Cash-Speichertechniken eingehen und noch ein bisschen was zum virtuellen Speicher sagen.
Das wird aber relativ einfach gestrickt werden.
Also das ist dann, sag ich mal,
immer so als Add-on dann
auch zum Selbernachlesen gedacht
Gut, Pipelining, was versteht man da drunter? Grundidee ist im Prinzip ganz einfach,
wenn man sagt, ich habe jetzt also eine große Aufgabe,
hier jetzt als Groß-F bezeichnet,
die jetzt also irgendwie eine Funktion,
Berechnungsvorschrift, was auch immer ist,
die man in Talschritte Klein-F zerlegen kann,
die dann jeweils parallel zueinander ausgeführt werden und wo dann zwischen diesen einzelnen Teilschritten oder Teilberechnung dann eben noch zusätzliche Register mit eingeführt werden.
Diese Kombination aus Talfunktionalität und Register ist dann das,
was dann also hier als Stage,
beziehungsweise im Englischen dann nachher so im Zusammenhang mit Pipelining
dann eben auch immer als Pipeline-Stage oder Pipeline-Stufe verstanden wird.
Gut, im Folgenden will ich so ein bisschen darauf eingehen,
welche Vorteile das hat, unter welchen Randbedingungen man damit arbeiten kann.
Eine von diesen Randbedingungen ist also hier unten im Prinzip dann auch schon genannt,
nämlich dass man sagt, also dass der Zeitbedarf jetzt für jeden dieser Talschritte FI letztendlich schon mal,
also deutlich größer sein sollte, als jetzt irgendwie die Zugriffszeit auf ein Register.
Also ganz nochmal zurückgesprungen bei dem Kapitel zu Schaltwerken,
Automaten hatte ich ja so ein bisschen was gesagt,
was man also typischerweise bei Registern eben auch sagt, okay,
die haben inhärent eine gewisse Verzögerungszeit, dann habe ich noch zusätzliche Zeitbedingungen durch Setup und Hohlzeiten.
Und wenn ich jetzt diese Gesamtzeitbedingungen nehme,
dann sollte das natürlich schon mal deutlich kleiner sein als das,
was jetzt also hier irgendwie an Verzögerungszeit in den jeweiligen Brablin-Stufen passiert,
weil sonst baue ich hier dann natürlich einfach erstmal sehr viel zusätzliche Zeit mit ein durch die eingebauten Register.
Gut, Grundidee, wie gesagt, dieses Konzept des,
hat ja eben schon genannt,
dass man eben durch diese unabhängigen Talberechnung erreicht,
dass quasi die Gesamtberechnung sukzessive durchläuft und zu jedem Zeitpunkt dann jeweils verschiedene Operationen gleichzeitig aktiv sind.
Und die Gesamtberechnung dann eben dadurch sukzessive entsprechend ausgeführt wird und
man dann eben logischerweise dann eben auch jeweils alle Stationen auslastet.
Das führt dann dazu, dass ich in dieser Pipeline dann
eben im Prinzip auch schon sowas wie eine Parallelität oder eine
gleichzeitige Berechnung von mehreren Aufeinanderfolgenden Berechnungsschritten mache.
Wenn ich klassische Beispiele sind, eben irgendwie sowas wie jetzt
Gut, wo kann man Pipelines einsetzen? Zum einen,
als arathmetische Pipelines, also gerade jetzt bei irgendwelchen arathmetischen Operationen,
die entsprechend sehr lange dauern, weil die,
also durch entsprechend große Schaltnetze, nur zu realisieren sind, passiert sowas typischerweise.
Also hier hatte ich als Beispiel jetzt eben genannt,
Multiplikation, Division oder eben auch bei Fließkomma-Operationen,
gilt aber eben genauso gut für auch beliebige andere Schaltnetze,
die man sich da ausdenken kann. Nicht. Und das,
was man da dann natürlich dann auch erreicht, ist,
dass wenn ich jetzt die Pipeline auslaste,
mehrere diese Operationen immer der Reihe nach hintereinander ausführe, das heißt,
die Pipeline dann also auch mal wirklich in jedem Talschritt und in jeder Stage,
also mit Daten füttern kann,
dass dann eben auch entsprechend der Durchsatz deutlich ansteigt.
Rechenbeispiele dazu folgende gleich. Zum anderen kann man sagen,
diese klassische von Neumann-Abarbeitung, Befehl holen, Befehl dekodieren,
Operanten holen, ausführen, Speicherzugriff, Rightback.
Das kann man dann eben auch der Reihe nach in eine Pipeline stecken,
ist dann eben das, was dann so als Prozessor-Pipeline gilt.
Und das, worauf ich jetzt eigentlich auch die ganzen folgenden Beispiele aufbaue,
bei realer Hardware, also normalen Prozessoren, wie wir sie jetzt haben,
hat man in der Regel eigentlich dann eine Kombination aus beidem.
Das heißt, ich habe zum einen bei der Abarbeitung der Befehle,
also im Pipe Planning,
ich habe aber auch in den Recheneinheiten,
gerade Fließkomma-Rechnerinhalten,
typischerweise dann eben auch noch so und
so viele Pipeline-Stufen mit drin
Gut, hier, jetzt nochmal das Beispiel,
wo jetzt also letztendlich erklärt wird, wie sich das jetzt
also mit dem Durchsatz beziehungsweise mit der Zykluszeit entsprechend verhält.
Also hier beispielsweise, also irgendeine Kombinatorische Logik benötigt,
300 Pico-Sekunden, wenn ich jetzt das am Eingang irgendwelche Werte anlege,
bis ich also entsprechend die ganzen Sachen jetzt also aufgrund irgendwelcher Gatterverzögerungszeiten,
also hier bis zu den Ausgängen
Fortgepflanzt haben, dann soll das Ergebnis in einem Register gespeichert werden,
wo ich dann eben auch eine bestimmte Verzögerungszeit habe.
Hier ist das Beispiel 20 Pico-Sekunden, das heißt also,
ich habe ein Delay von 320 Pico-Sekunden,
kann demzufolge diese Schaltung dann mit gut drei Gigahertz-Takten,
beziehungsweise wenn ich jetzt sage,
mit jedem Tag wird dann quasi ein neuer Wert verarbeitet,
ist man dann eben bei einem Durchsatz von
3,12 Giga-Operationen pro Sekunde
So, wenn ich jetzt, diese Gesamtberechnung von den 300 Pico-Sekunden aufteilen kann,
in drei einzelne Teile, A, 100 Pico-Sekunden, sprich eine dreistufige Pipeline aus dem Ganzen mache.
Hier jetzt noch zwei zusätzliche Registereinfüge. Sieht man,
dass jetzt für die Gesamtberechnung, das heißt also das,
was jetzt wirklich als Latenz stattfindet, das heißt,
wenn ich also mit einem Wert anfange zu rechnen,
bis ich das Ergebnis hier am Ende in dem Ausgang Ausgangsregister stehen habe,
dass das logischerweise zunimmt, sprich, ich habe hier zwei zusätzliche Pipeline-Register,
nicht demzufolge, jetzt nicht mehr 320, sondern 360,
Pikosekunden, das war es dann also wirklich die Latenz ist.
Was man aber jetzt eben hier machen kann in so einer Aufteilung,
dass ich dann eben sage, alle 120 Pikosekunden,
sprich das war es jetzt also,
die Verzögerungszeit einer einzelnen Pipeline-Stage ist,
kann ich eben jeweils eine neue Operation anfangen und mache hier drin dann eben quasi drei Berechnungen Parallelen.
Hier ist das Ganze also auch nochmal zeitlich aufgezeichnet, also sprich ohne Pipeline.
Eine Berechnung muss ich eben wirklich warten, bis die komplett abgeschlossen ist.
Wenn ich hier so eine dreistufige Pipeline habe,
das entsprechend gleichmäßig verteilt habe,
dann sieht man also mit dem ersten Tag kann ich,
wenn das Ergebnis, also jetzt hier in dieser,
durch die erste Stage gelaufen ist,
dann eben die nächste Berechnung anfangen und der zweite Teil der
ersten Berechnung läuft dann eben entsprechend eine Stage weiter
Und dadurch habe ich dann eben im Idealfall das,
was dann hier in der Mitte ist, dann eben wirklich alle drei Stages gefüllt.
A, B und C. Und da dann eben auch entsprechende drei Operationen in Bearbeitung.
Hier ist das Ganze jetzt also auch nochmal quasi zeitlich aufgezeigt,
wie man sich das also vorstellen muss.
Also wir befinden uns jetzt hier irgendwo an einer Zeit kurz vor der Taktflanke.
In dem Register hier stehen jetzt also irgendwelche Werte,
die haben sich dann irgendwie durch die Logik.
Quasi fortgepflanst. Ich habe jetzt hier also irgendeinen Ausgangswert,
der jetzt also quasi hier am Eingang dieses Registers anliegt.
Gleichzeitig habe ich hier am Eingang irgendwelche Werte,
die also hier durch diese Talberechnung in dieser Stage dann eben dazu geführt haben,
dass dort irgendwas ist, liegt am Eingang.
Dieses Registers, und wenn jetzt eben letztendlich der Tagdimpuls kommt,
das soll ja sich eben vorderflankengesteuert sein, habe ich genau diese Situation,
dass jetzt eben die Werte hier in den Registern gespeichert sind.
Gleichzeitig kann ich dann eben hier am Eingang sagen,
dort kann dann eben auch ein neuer Wert schon anliegen,
der dann eben im Idealfall jetzt hier aus irgendeinem vorhergehenden Register kommt.
Und das, was also jetzt hier in diesen Registern ist,
würde sich dann eben irgendwie durch diese Logik ausbreiten,
was dann also irgendwann später der Fall ist.
Also die Werte verändern sich hier drin und vor der nächsten Tagflanke
habe ich dann eben die jeweiligen Berechnungen wieder an den Eingängen,
der Pipeline-Register und die würden dann eben mit dieser Tagflanke nach 360 Ähm,
ja, Nano, Pico, wie viel auch immer.
Ähm, naja, Pico-Sekunden sollten das dann ja sein.
Dort dann eben übernommen werden. Gut, das ist der Idealfall
. Was sind jetzt letztendlich immer so ein bisschen Limitierung,
beziehungsweise worauf muss man achten, wenn man solche Pipelines aufbaut?
Zum einen funktioniert das in so einem Idealfall natürlich immer nur, wenn ich die einzelnen Pipelines-Stages wirklich gleich groß dimensionieren kann. Das heißt,
wenn ich jetzt nicht Einheitliche Verzögerung habe,
wie das also hier in diesem Beispiel angedeutet ist,
wo ich also jetzt hier irgendwie so einen Block in der Teilberechnung drin habe,
der dann also 150 Pikosekunden benötigt,
nicht einer hier mit 100, andere mit 50,
dann sieht das eben so aus,
dass ich letztendlich die Tagdrahte natürlich immer noch,
also aus diesen 150 plus 20 Pikosekunden ergibt und man demzufolge in der Taktung dann jetzt eben nicht so viel gewinnt, wie das jetzt bei der gleichmäßigen Verteilung der Fall wäre.
Und was dann eben hier auch entsprechend angedeutet ist,
also die entsprechend kürzeren Teile, ja gut, da gewinnt man nichts,
sondern irgendwann ist sie das Ergebnis fertig und dann warten,
liegt also hier quasi am Ahlengang der richtige Wert an,
aber das Ganze wartet dann eben immer noch auf den Tag,
der letztendlich also über diese Stufe dann bestimmt wird.
Deswegen als Quintessenz, die Schaltung sollte eben entsprechend live-mäßig und ertalt, positioniert sein.
Die andere Sache ist, wenn man jetzt natürlich sagt,
okay, die weit unterteile ich das, wie viele Pipelines-Stages baue ich da ein?
Eingangs hatte ich ja gesagt, das Ganze funktioniert eben wirklich optimal,
wenn ich jetzt in jeder Stage für diese Talberechnung eben irgendwas habe,
was zumindest schon mal ein Stück größer ist als das,
was ich also jetzt hier an zusätzlichem Overhead in den Registern verbrauche.
Und das ist also jetzt hier einge, mal dargestellt,
beziehungsweise entsprechend durchgerechnet, wo jetzt also quasi dieser Block mit der Logik,
mit den 300 Pikosikonen jetzt quasi in sechs Pipeline Stages unterteilt wurde.
Nicht, was man jetzt sieht, ist zum einen,
dass also die Gesamtlatenz jetzt für eine Berechnung, jetzt also natürlich,
dass ich hier entsprechend nochmal fünf zusätzliche Register drin habe von ursprünglich ohne Pipelining,
320 Pikosekunden jetzt auf 420 Pikosekunden angewachsen ist.
Man hier zwar also noch entsprechend durch diese Parallelbearbeitung beeindruckenden Durchsatz bekommt,
aber Wie gesagt, also der Anteilige Teil,
jetzt ist er hier für die Register dann eben entsprechend stark gewachsen ist,
was dann also hier einfach mal durchgerechnet ist,
wo ich dann eben jetzt sage,
wie groß ist also quasi jetzt der Register-Overhead im Verhältnis zur Tagperiode.
Wenn ich jetzt eben nur ein Register habe,
in diesem Bereich ein Beispiel,
liegt das eben bei 6,25% Prozent in der Dreieregister-Variante,
dann eben schon bei 16,67%.
Und wenn ich dann eben,
also wie jetzt hier so auf sechs Register gehe,
dann bewegt man sich dann also schon auf die
29% hin
Hat dann eben entsprechend viel Overhead durch die Register. Das heißt,
da gibt es dann eben irgendwo so einen gesunden Mittelweg,
wo man eigentlich dann sagen muss, okay,
also ab da lohnt sich das einfach jetzt nicht mehr noch weiter zu unterteilen,
auch wenn man dadurch natürlich erstmal rein rechnerisch den Durchsatz immer weiter erhöhen kann.
Gut, die andere Sache ist dann,
was die eigentlich auch noch mit reinkommt.
Also irgendwann werden die Taktperioden dann also auch schon so klein,
dass man sagen kann, da wird das dann also auch sonst,
sagen wir mal,
technisch schon vergleichsweise schwierig oder anspruchsvoll
in der Realisierung
Gut, und das Kritischste eigentlich ist, wenn ich Datenabhängigkeiten habe.
Ganz triviales Beispiel, wir nehmen einen Zähler. Das heißt,
die kombinatorische Logik macht nichts anderes als plus eins zu rechnen.
Und ich habe einen Register neben der Zählerwert steckt.
Da kann ich kein Pipelining machen. Ich meine,
dann nützt es mir jetzt nichts,
wenn ich jetzt also dieses plus eins rechne,
also irgendwie in drei oder fünf oder zehn Pipeline-Stufen unterteile,
weil quasi das,
was sie in der Berechnung stattfindet,
natürlich vom Inhalt dieses Registers abhängt
Und ich da dann eben wirklich einmal komplett durch sein muss und dann erst,
wenn ich da so mein Endergebnis habe, die nächste Operation anfangen kann.
Nicht das Gleiche gilt jetzt eben auch, also hier ist es
dann eben beispielsweise dann auch nochmal angedeutet, was da ist.
Wenn ich jetzt das hier so ein Pipelining einbauen würde,
dass eben letztendlich das Ergebnis mit ihm dann weitergerechnet wird,
was dann also je nach drei Takten hier drin steht,
natürlich inzwischen schon zwei weitere Pipeline-Schritte durch angefangen wurden,
wo dann also im Prinzip dann mit irgendwelchen unsinnigen Werten gerechnet wurde.
Das heißt, da sieht man dann also wirklich massiv,
wie es sich also jetzt durch Pipelining, das gesamte Systemverhalten ändert.
Und das sind dann eben eigentlich wirklich die Einschränkungen,
wo man sagen kann, wenn ich solche Fälle habe mit Rückkorplung,
funktioniert Pipelining nicht. Eben in den Beispielen arathmetische Pipeline,
sprich, ich habe jetzt also irgendwelche Werte,
die sollen addiert werden oder multipliziert werden oder ich möchte irgendeine Fließkomma-Berechnung machen und da kommt was raus.
Nicht, dass es rein funktional gesehen ein reines Schaltnetz,
da geht das
Nicht, aber durch die Rückkopplung habe ich dann eben formal gesehen ein Schaltwerk.
Da kann ich dann, also sagen wir jetzt,
so einfach kein Pipelining machen. Und bei Prozessor Pipelines,
da werde ich dann also noch ein bisschen genauer drauf eingehen.
Da gibt es dann nämlich, sagen wir mal,
so ähnliche Effekte, wenn ich letztendlich sowas wie Datenabhängigkeiten habe.
Ansonsten kann ich immer sagen, okay,
ich habe jetzt meine einzelnen Instruktionen,
also jetzt irgendwelche Assembler-Befehle und so lang die alle unabhängig voneinander sind,
kann ich die immer brav irgendwie in die Pipeline reinschmeißen
Ich bloß, wenn ich das also jetzt Pipeline-mäßig mache und
das Ergebnis soll beispielsweise in Register 2 abgelegt werden und
ich möchte in der nächsten Pipeline-Stufe mit Register 2 weiterrechnen,
dann habe ich eben so eine Datenabhängigkeit,
was dann letztendlich so eine Situation hier entspricht.
Folgt aber gleich noch ein bisschen genauer,
da zuerst nochmal die Vorstellung,
wie jetzt also so ein Prozessor-Pipelining aussieht,
beziehungsweise hier dann als Befehls-Pipeline beschrieben
Ich habe mich jetzt hier eigentlich so an die Terminologie von Patterson und Hennessey gehalten,
also wie das jetzt immer genannt wird,
wo man im Prinzip dann so eine einfache,
fünfstufige Pipeline aufbaut, wie gesagt,
bei modernen Prozessoren sieht das dann immer noch ein bisschen anders aus,
aber das ist erstmal so für das Grundverständnis eigentlich immer am einfachsten.
In der ersten Stufe habe ich eben Instruktionen holen,
die wird in das Befehlsregister geladen.
Anschließend gibt es dann eben dekodierende Instruktionen und holende Operanten,
wenn es sich also beispielsweise um irgendwie welche arathmetisch logischen Rechenoperationen handelt.
Meistens werden diese beiden Schritte dann immer eine einzige Pipeline-Stufe geparkt.
Deswegen fehlt an dieser Stelle hier nämlich also auch schon dieser Trennstrich.
Nicht dann eben diese Execute,
wo dann also quasi die Alu jetzt also irgendeine Berechnung ausführt,
dann gegebenenfalls dann eben noch ein Speicherzugriff,
um Daten zu laden oder in den Speicher zurückzuschreiben.
Also das ist jetzt hier dann natürlich auch immer erst mal entsprechend einfach abstrahiert,
also direkt jetzt auf so eine MIPS-Risk-Architektur abgebildet.
Das heißt, ich kann nur mit, anders als es
beim X86-Sembler eben nur mit Lot und Store befehlen, auf den Speicher zugreifen.
Das heißt also, etwas aus dem Speicher in Register lesen
oder außen Register in den Speicher schreiben. So,
jetzt kann man sich immer noch fragen,
warum passiert überhaupt erst mal dieser Memory-Access soweit hinten,
weil entweder ich mache eine Arithmetisch-logische Operation,
wo ich was rechne Oder ich habe ein Load oder einen Store-Befehl,
wo ich auf einen Speicher zugreife.
Das heißt, eigentlich kann das ja irgendwie parallel
zu diesem Execute ausgeführt werden. Hat jemand eine Idee,
warum das vielleicht der Fall ist,
dass man also typischerweise ein Memory-Access immer noch weiter nach hinten verschiebt,
hinter das Execute hinter die irgendwelche Alu-Operationen?
Bitte? Unter anderem noch länger,
das haben wir noch gar nicht besser gemacht
Also die Antwort war jetzt, war ein Memo Speicherheadscore,
also länger dauern kann.
Und gerade, wenn ich also mit virtuellem Speicher arbeite und
dass er sich dann irgendwo auf der Festplatte befindet.
Das ist eigentlich dann wieder ein anderes Problem oder Fragestellung.
Das ist dann etwas, was im übernächsten Kapitel dann zur Speicherhierarchie
noch folgen wird. Hier gehe ich erstmal davon aus,
dass ein Speicherzugriff genauso lange dauert,
wie es also irgendeine beliebige andere Operation in der Pipeline
Sonst noch jemand irgendwelche Ideen? Das ist jetzt weit hergeholt
und das ist jetzt hier also auch natürlich,
also erstmal reine Riske-Architektur, aber bei dem,
was wir im Prinzip jetzt beim Cis-Gerechner gemacht haben,
beziehungsweise beim X86-Assembler.
Wie war das dort mit Speicher zugriffen?
Was passierte dort teilweise noch?
Langs heren
Also die Antwort, beziehungsweise das Stichwort war Adressberechnung.
Da gab es ja immer diese Moody, wo man sagen kann,
also Inhalt eines Registers plus Inhalt eines anderen Registers skaliert plus Offset.
Also sprich, dass die Adresse, auf die
irgendwie zugegriffen wurde, lesend oder schreiben noch berechnet wurde.
So, und wer soll das berechnen? Berechnen tut das
die Alu. Wenn ich demzufolge kann es eben sein,
dass eben auch wirklich bei reinen Operationen antwortweise völlig richtig,
dass eben auch bei reinen Operationen,
die jetzt eben nur auf den Speicher zugreifen,
hier noch irgendwelche Adressen berechnet werden.
Deswegen eben auch diese Trennung. Gut, ein Rideback ist dann eben letztendlich,
dass entweder ein Alu-Berechnung in Register zurückschreiben oder irgendwas,
was sie aus dem Speicher gelesen habe,
wenn ich einen Load-Befehl ausgeführt habe.
Gut, das ist jetzt mal so das allgemeine Schema,
an dem ich mich ja jetzt heute noch so ein bisschen entlanghangeln werde.
Und jetzt kann man eben sagen, je nach Arthur-Operation
sind es unterschiedlich viele Schritte notwendig.
Das ist nicht immer zwingend alle. Es gibt dann also
sowas wie ja auch Zumindest ein Assemblabal-Spielen schon mal aufgetaucht,
so ein No-Op-Befehl, also sprechen Befehle, dann nichts weitermacht.
Der wird letztendlich nur geholt und dekodiert und hier tut sie
nichts und hier tut sie nichts und hier tut sich auch nichts.
Nicht so was wie ein unbedingter Sprung.
Auch da muss ich eigentlich nur den Programm-Counter entsprechend mal modifizieren.
Das heißt also auch da findet ihr jetzt eigentlich nicht zwingend
einen Speicherzugriff oder einen Registerzugriffsstaat.
Also man kann jetzt immer sagen,
je nach Art der Instruktion sind jetzt eben unterschiedlich viele von
diesen Schritten notwendig
Nee, aber das ist erstmal so grob das Schema für Pipelining,
dass ich jetzt, also hier in dem Beispiel immer noch ein bisschen weiter behandeln werde.
Wie gesagt, hier auch nochmal die Anmerkung nicht.
Also in der Regel bei den Prozessoren sind diese Schritte dann
eben auch noch weiter unterteilt. Nicht, das heißt,
da gibt es dann eben für sowas wie Instruction-Fetts dann teilweise
auch schon zwei oder drei Stages in der Pipeline.
Wobei das dann also jetzt wieder mit der X686-Architektur noch zusammenhängt.
Aber gut, da gibt es dann noch entsprechende Beispiele. Gut,
hier redet es also auch nochmal die Folie aus dem Buch von Patterson und Tennessee,
nicht wo man jetzt eben letztendlich hier sieht, hier ist also irgendwelche Assembler-Befehle,
das ist jetzt hier natürlich wieder was ganz anderes,
das ist jetzt hier MIPS, als Assembler genommen,
also sprich ein ganz anderer Prozessorarchitektur.
Ich habe letztendlich Load Word ist dann eben im Prinzip hier ein Speicherzucker von hier ein Register,
in das das Ergebnis dann quasi geschrieben wird.
Und wenn ich jetzt erstmal kein Pipelining mache,
kann man sagen, gibt es eben irgendwie eine bestimmte Zeit,
beispielsweise 800 Pico-Sekunden, die das Ganze dann letztendlich dauern würde.
Wenn man das dann eben in Pipelining mit diesen Fünf Stufen quasi verschränkt,
erreicht man dann eben,
dass das Ganze jetzt bei diesen drei Befehlen dann eben natürlich deutlich schneller,
deutlich effizienter arbeiten würde.
Und wie gesagt, die Zykluszeit in diesem Fall,
also die mit 200 Pico-Sekunden angegeben wird,
dann eben letztendlich durch den Zeitbedarf der längsten Pipeline Stage
Gut, dem zufolge kann man sich dann eben vorstellen,
diese klassische, fünfstufige Pipeline erstmal mit so einer Grafik,
Instruktion holen, Pipeline-Register, dekodieren, nächstes Register,
Execute, das heißt, hier habe ich jetzt irgendwie Alu,
irgendwie welche Operanten A und B,
irgendwo wird natürlich auch noch die Steuerinformation letztendlich von dem Befehl,
der dann also quasi hier gerade drin ist,
in die Aluralen gehen, was da jetzt gerade gerechnet werden soll,
dann eben sowas wie ein Speicherzugriff, und dann eben gegebenenfalls an Ebene mit diesem Rideback dann wieder zurückschreiben in Register.
Nicht, dass erstmal so grob die Grundidee dieser Risk-Architekturen,
man kann dann eben sagen,
durch die Einsprung des Pipelinings ist eben der Durchsatz um den Faktor 3 bis 5 gestiegen,
verglichen jetzt mit einer rein seriellen Ausführung,
nicht also hier unter diesen Links verbergen sich dann gleich die entsprechenden Grafiken,
wo das also auch nochmal aufgezeigt ist,
ich komme da gleich noch darauf zurück und das ist eigentlich ein Vergleichsweise guter Kompromiss,
zwischen dem, was man also da jetzt mit diesen Pipeline erreichen kann und dem,
was jetzt eben an zusätzlichem Hardwareaufwand vielleicht noch betrieben werden muss.
Wie gesagt, also alles hier folgt jetzt eigentlich, also aus diesem Buch von Patterson und Fairmacine. Grundidee,
wie gesagt, dabei Risk, Reduced Instruction Set,
da wird eben diese Pipeline direkt umgesetzt,
entwickelt wurde das Ganze Mitte, Ende der 80er Jahre,
quasi hier bei den ersten MIPs-Prozessoren umgereichen gesetzt,
dann später bei den Spark-Prozessoren von Sun entsprechend weiter gepflegt,
dann Kann man eben sagen, dass das,
was ursprünglich mal als Ciska-Architektur entwickelt wurde,
also sprich beispielsweise X86 oder die Motorole 68 irgendwas, Prozessoren,
haben dann eben auch angefangen, naja, nachdem er festgestellt hatte,
ja, das hat noch gewisse Vorteile und man kann damit also noch dort gewisse Leistungssteigerung erreichen, so was eben entsprechend auch umzusetzen.
Also sprich, bei X86-Prozessoren war das dann letztendlich Ende der 80er Jahre, wo dann eben Pipe-Lining-Konzepte Einzug gehalten haben. Und da ist dann die Grundidee,
weil ursprünglich, oder ist das ja eben ein Sisk-Rechner,
wo ich dann also letztendlich komplexe Anweisungen habe,
die erstmal ganz klassisch dann eben über so ein Mikroprogramm-Steuerwerk in mehreren Schritten ausgeführt werden,
dass man dann eben sagt,
jede von diesen Disk-Anweisungen wird eben bei der Dekodierung eben in so eine ganze Folge von kleinen Teilanweisungen unterteilt,
die dann eben riskähnlich abgearbeitet werden, nicht mit dem Vorteil,
dass man dann eben sagt, gut, das alles,
was an Alterssoftware letztendlich auf diesem Befehlssatz implementiert wurde,
ist eben nach wie vor immer noch lauffähig.
Gut, hier ist jetzt das Beispiel von den Erfindern dieser Architektur,
sag ich mal so, in der Form ohne Pipelining,
nicht wie man sieht, schön übersichtlich.
Also Grundidee ist, ich habe hier PC-Programm-Counter,
habe jetzt hier mein Instruktionsspeicher und letztendlich das was ja als längster Fahrt drin ist,
kann man sich jetzt vorstellen, eine Operation,
sowas wie ein Lotbefehl, wo ich also eine Adresse berechne,
von dieser Adresse aus dem Datenspeicher etwas lese und das Ergebnis in einen Register transferiere.
Das heißt, ich habe hier irgendwo im Program-Counter Programm zählt jetzt quasi die Adresse der Instruktion,
die Instruktion wird geholt, das Ganze wird dann hier dekodiert,
aus dieser Instruktion wird quasi dann rausgelesen,
Es handelt sich um einen Lotbefehl,
der oder Innenregister 2 beispielsweise steht die Adresse und es soll eben noch irgendwie als Offset der Inhalt von Register 3 hinzuaddiert werden.
Nicht demzufolge werden dann also quasi hier aus der Registerbank jetzt
also die beiden Operanten für diese Adressberechnung geholt.
Das Ganze läuft dann durch die Alu.
Ich berechne die Adresse, von der
ich lese
Irgendwann kommt dann also hier das Ergebnis an und wird dann
also über einen entsprechenden Multiplexer in die Registerbank reingeschrieben.
Nicht, das ist also das, was hier letztendlich als
längster Fahrt dort dann in so einem Beispiel wirksam werden würde.
Frage. Das ist ja im Prinzip so etwas identifizierende Scheinbild
Genau. Das, also die Frage war jetzt,
wenn man das jetzt also eben so als Schaltbild interpretiert,
was ja noch eben auch ist,
habe ich hier jetzt natürlich anders als bei dem,
was ich immer so vorher als Fonnorman-Konzept verkauft habe,
wo ich immer gesagt habe, naja,
also nicht gemeinsamer Speicher für Instruktionen und Daten.
Nicht hier haben wir es auf einmal getrennt,
das Speicherinstruction,
Memory und Data Memory
Frage werde ich nachher noch beantworten,
weil dazu gibt es dann nämlich explizit dann nochmal ein bisschen was dazu,
weil das ist nämlich immer eine der Grundvoraussetzungen,
mit also an dieser Stelle oder überhaupt,
also sowas wie Pipelining funktionieren kann, dass ich getrennten Instruktions- und Datenspeicher habe,
war also auf einem gemeinsamen Speicher, würde das nachher einfach nicht gehen,
weil ich dann also quasi in einem Takt zweimal lesen müsste oder zwei Speicherzugriffe machen müsste,
ist dann auch der Grund, wenn man da jetzt,
also weiterdenkt und nachher dann eben auf die Speicherhierarchie kommt,
weswegen man in den Prozessoren quasi als First-Level-Cash dann auch immer sieht,
L1, I und L1, D. Dort sind dann nämlich wirklich Instruktions- und Datenspeicher getrennt.
Ich meine, das ist dann eben wirklich das,
wo man sagt, okay, das sind die 30 Jahre Entwicklung,
die sich jetzt vom ursprünglichen von Neumann-Konzept und dem,
was jetzt also irgendwie so ein mit 4 Gigahertz laufender aktueller Prozessor ist getan,
hart, wo man eben sagt, okay,
also direkt armen Prozesse habe ich eben getrennte Speicher,
mit ganz anderen Zeit Bedingungen und dazwischen dann eben noch mehrere Ebenen Cashes und dann kommt erst der Hauptspeicher,
weil, gut, das ist dann eben etwas,
was dann nachher noch in der Speicherjerarchie kommt, war,
also im Prinzip kann man jetzt eben sagen,
Prozessor läuft mit knapp vier Gigahertz Zugriff auf den Hauptspeicher, selbst wenn ich mir also irgendwie den sündhaft teuren nur für über takter geeigneten Speicherriegel kaufe, ist immer noch um Größenordnung langsamer.
Und ich möchte ja meinen Prozessor nicht ausbremsen.
Und das ist etwas, was dann eben diese Speicherhierarchie abfängt.
Aber da werde ich dann nochmal genauer drauf eingehen.
Können wir das noch? Ja. Das ist das Beste,
was bei dir meinte, unsere Tech-Frequenz-Kapferlung, unsere Zuschauer,
die Wortgeister,
die wir haben
Ja, gut, also gut, also zum einen ist,
also sind die höheren Taktfrequenzen nur mit Pipelining möglich,
zum anderen ist es aber eben auch so,
dass man, sagen wir jetzt,
also in den letzten Jahren eben keine so drastischen Zuwächse in den Taktraten mehr hatte,
sondern das eigentlich eher moderat ist,
weil das sind dann jetzt wieder andere Probleme,
wo ich dann eigentlich auf irgendwas eingehen müsste,
was ich jetzt also nicht weiter vorgestellt habe,
wo man dann nämlich,
also im Prinzip wirst du in die Elektrotechnik geht,
dass man also sagt,
mit den höheren Taktraten sinkt oder steigt eben einfach die Leistungsaufnahme oder Leistungsverbrauch,
quadratisch,
wenn nicht sogar kubisch und es lohnt sich dann einfach nicht mehr jetzt,
also immer noch weiter und noch schneller zu takten,
weil man kriegt die Schaltung nicht mehr gekühlt
Gut, ja, das Ganze jetzt in dieser klassischen Form
fünfstufige Pipeline ist jetzt also hier im Prinzip eine vergleichbare Grafik,
wo dann aber eben hier die jeweiligen Pipeline-Register einfach nur entsprechend
eingezogen werden und man dann eben genau diese Trennung in diese fünf Stufen erreicht hat.
Und was dann dort auffällt, ist, dass eben die eigentlichen Hardware-Komponenten,
also sprich Registerwerk, die ganzen Rechenwerke und alles,
was da da drin ist, nicht weiter verändert worden sind,
sondern dass man einfach als Zusatzaufwand jetzt wirklich nur zusätzliche Pipeline-Register einführen musste,
wo dann eben teilweise die dekodierten Befehle,
irgendwelche Zwischenergebnisse und irgendwelche intern benötigten Signale zwischengespeichert werden.
Und das, was dann eben als längster Fahrt irgendwo auftritt,
ist dann eben nicht mehr quasi hier einmal irgendwie komplett
durch bis hier in das Register rein,
sondern eben irgendwo nur noch Pipeline-Stufe und eine Ebene davor es an den Ausgang irgendeines anderen Registers.
So, das hört sich eigentlich erst mal ganz toll an.
Wie beeinflusst uns als Informatiker, als Programmierer das jetzt eigentlich?
Sprich, wie wirkt sich das irgendwie auf die Software aus?
Hat es überhaupt Auswirkungen? Ich glaube,
bislang kann man sagen, ja, irgendwie so denkt so,
Übungsaufgaben, SE oder Sonstiges. Mich würde auch alles,
was ich vorher irgendwo ja mit dem Assembler vorgestellt habe,
ist da irgendwie mal Pipelining aufgetaucht.
Behaupte ich jetzt erst mal nein
Nicht dem zufolge kann man da dann eigentlich als Quintessenz dann auch schon mal mit rausnehmen,
im Großen und Ganzen beeinflusst es die Software nicht,
was nur sein kann,
eben aufgrund solcher Effekte wie Datenabhängigkeiten und so weiter,
dass wenn man sich den Assembler-Code ansieht,
da dann eben sowas entsprechend,
gegebenenfalls berücksichtigt werden muss,
sprich,
dass da dann der Compiler um die Architektur
und um den Aufbau der Pipeline
eigentlich wissen muss
Gut, vorher hier noch mal einige Begriffe. Pipeline Stage hatte ja schon gesagt,
Pipeline Machine Cycle, bezeichnet man dann quasi immer,
wenn ich jetzt also einen Befehl irgendwie der Reihe nach,
Instruction, Fage, Instruction, die Code, Execute,
Memory, weiterschiebe.
Durchsatz und Latenz hatte ich also auch schon entsprechend erklärt,
also spreche Durchsatz, wäre dann eben die Anzahl der Instruktionen,
die eben jedem Takt abgeschlossen werden können.
Latenz dann ebenentsprechend die Zeit, die es a eine Instruktion
benötigt, um komplett ihre Gesamtberechnung zu beenden.
Gut, was bringt mir das? Dadurch,
dass ich das Gesamtschalten jetzt entsprechend kleine Blöcke aufgeteilt habe,
einen höheren Takt. Im Idealfall, also sprich,
wenn ich jetzt mit jedem Takt einen neuen Befehl in die Pipeline reinfüttern kann,
habe ich also entsprechend einen hohen Durchsatz,
eine deutlich bessere Performance.
Das heißt, der Computer rechnet einfach erstmal schneller
Wenn ich das jetzt so aufbaue, wie das da
jetzt angedeutet ist, also mit diesen vier zusätzlichen Pipeline-Registern,
dann ist das also vergleichsweise billiger.
Speed-up, womit ich da was erreiche. Das heißt,
ich habe da jetzt also wirklich nur relativ wenig Zusatzaufwand.
Und was sie eben auch schon angedeutet habe,
also das Pipelining ist so, zumindest für den Programmierer,
direkt erstmal nicht sichtbar. Was allerdings sein kann,
was ich also dann eben auch gleich noch vorstellen werde,
sind dann also Effekte,
die sich also aus Daten oder gerade dann aus Kontrollabhängigkeiten heraus
ergeben können
Nachteile, die Rechenbeispiele hatten das ja auch schon gezeigt,
die Latenz, das heißt,
vom Anfang eines Befehls bist du zu dessen komplette Abarbeitung,
wird nicht verbessert, sondern bleibt bestenfalls gleich,
weiß ich dann also auch schon mal, bezweifeln würde.
Also in der Regel wurde ja eigentlich doch mal ein ganzes Stück größer.
Und man muss natürlich die Pipeline eben entsprechend sauber dimensionieren,
dass also wirklich die Pipeline-Stufen alle gleich groß sind oder gleich lang brauchen.
Also jede war als, weil natürlich der Gesamtakt durch die langsamste Pipeline-Stufe vorgegeben ist.
So, und was dann jetzt eben auch noch mit reinkommt,
dass ich zusätzliche Zeiten oder zusätzliche Takte brauche, um die Pipeline zu füllen bzw. zu lehren.
Also früher war es so ohne Pipelining, dass ich sagen kann, um einen Befehl oder eine Operation abzuarbeiten, am Eingang einlegen, einen Takt geben, fertig. Nicht,
wenn das Ganze jetzt eben durch eine mehrstufige Pipeline läuft,
das Programm ist fertig,
der letzte Befehl wurde in die Pipeline reingeschoben,
muss ich eben noch dafür sorgen,
dass der natürlich auch noch komplett durch die Pipeline hindurch wandert,
das heißt, dass entsprechend viele Takte hinterherkommen,
nicht wobei man sich dann ja eben fragen kann,
okay, was wird auf der anderen Seite,
wenn die Pipeline jetzt nochmal reingefüttert nicht und da hast du eben dann zum Beispiel die Gründe,
warum es in den Befehlssätzen sowas wie so ein No-Orp,
also No-Operation-Befehl gibt,
nicht den man dann also Beispielsweise an das Ende eines Programms einfach nur irgendwie nur parkt,
um zu sorgen,
dass also der letzte sinnvolle Befehl dann eben noch durch die Pipeline durchwandert.
Nicht, aber dass es dann eben entsprechend auch zu berücksichtigen.
Gut, was kann man damit gewinnen? Mich also rein
technisch kann ich das jetzt hier natürlich erstmal wunderschön durchrechnen.
Und zwar, wenn ich sage, ich habe jetzt ein Programm,
bestehend aus Endinstruktionen. Ich habe eine Pipeline mit Car-Pipeline-Stufen,
dann kann ich sagen, okay, Ohne Pipelining brauche ich in Marca-Taktzyklen,
wenn ich Pipelining habe, und entsprechend viele eben parallel mache,
würde ich K plus N, minus 1, Zackzyklen benötigen.
Demzufolge kann man dann eben sagen,
Speedup, also quasi der Quotient,
von dem mit Pipelining zu ohne Pipelining ergibt sich dann eben dadurch,
dass sich das dann hier einfach entsprechend durcheinander teile
Wobei man dann eben auch sagen kann,
wenn die Anzahl der Instruktionen hinreichend groß ist,
beziehungsweise jetzt hier beim Grenzwert entgeht, gehen unendlich,
kann ich dann eben sagen,
dass der Pipeline Speedup quasi dann gleich der Anzahl der Pipeline stufen wird.
Das war dann eben auch einer der Gründe,
weswegen man natürlich erstmal rechnerisch sagen kann,
beziehungsweise in diesen einführenden Rechenbeispielen sieht man das dann eigentlich auch schon.
Nicht je mehr Pipeline-Stufen ich habe,
um so mehr Durchsatz kriege ich durch
Was dann letztendlich den Speed-up beeinflusst. Ich nehme es so vor,
kann man sagen, okay, für eine große Pipeline-Tiefe,
also eine große Anzahl an Pipeline-Stufen kann ich,
erreiche ich dann eben eine entsprechende Verbesserung und das ist dann eben die,
der zweite entscheidende Faktor für hinreichend lange Instruktionssequenzen.
Das heißt, das Programm muss dementsprechend viele Anweisungen haben,
die dann eben auch direkt nacheinander in die Pipeline hineingefüttert werden können.
So hört sich eigentlich erstmal
ganz gut an
Nachteil bei dem Ganzen ist,
was sie dann nachher auch noch kurz sagen werde, beziehungsweise,
wenn wir jetzt nochmal wieder ein paar Kapitel zurückgehen, Instruction Set Architecture.
Da gab es ja so diese Statistiken, wie häufig welche Instruktionen sind.
Eine der am häufigsten genützten Instruktionen ist ein Sprung.
Und wenn ich sowas wie Sprünge oder bedingte Sprünge habe,
und da bricht das eben genau diesen linearen Abfluss von irgendwelchen Instruktionen,
demzufolge kriege ich an den Stellen dann eben auch Probleme mit irgendwelche aufeinanderfolgende Instruktionssequenzen,
die ich dann eben immer so danach in die Pipeline reinschieben kann.
Nicht, das verbirgt sich dann eben unter anderem dann eben schon hinter dem,
was sich dann hier als Daten- und Kontrollabhängigkeiten bezeichne, also Datenabhängigkeiten,
es ist eine, es kommt gleich ein paar Beispielkontrolleabhängigkeiten,
sind im Großen und Ganzen sowas wie bedingte Sprünge oder,
und zwar als Phase noch ein paar andere zusätzliche Effekte.
Und was hier in dieser Rechnung natürlich nicht mit drin
ist,
ist sowas wie Register-Overhead
Ansonsten kann man natürlich jetzt mit dieser Formel wunderbar beigehen und sagen,
hua, nehme ich Gnuplot, ich plotte mir das,
dass ich mehr so auf und sage jetzt, okay,
wenn ich jetzt hier also sage, zehn Pipeline-Stufen,
und ich habe jetzt hier unten aufgetragen,
die Anzahl der Befehle, nicht,
dass man dann also letztendlich sich als Grenzwert dann also irgendwo an den Speedup 10 heranhangelt,
wobei man dann eben hier auch schon sieht, nicht,
also sagen wir mal so ab 20 Befehlen,
Bin ich dann also hier irgendwo so schon in dem Bereich von einem Speed-Up von sechseinhalb oder so.
Und je länger die Befehlssequenzen sind, umso größer wird,
da dann eben entsprechend der Speed-Ups. Das ist jetzt eben
einfach nur genau diese Formel genommen und einfach stumpf durchgerechnet.
Gut, das führt eben erstmal dazu, dass man sagt,
naja gut,
größere Pipeline-Tiefe
Ich unterteile die Gesamtberechnungen immer kleinere Teile, kann dann das Ganze auch immer schneller takten, habe immer weniger Logik zwischen den Registern. Das führte eben zusammen mit dem technischen Fortschritt,
den man da jetzt natürlich auch nicht vergessen darf,
dazu, dass wenn man jetzt also mal so vergleicht,
von einem 80-386, Mitte der 80er Jahre,
nicht wo man sagen konnte, Perkfrequenz im Bereich von 33 Megahertz, bis,
sagen wir mal so, in die Anfang der 2010,
nicht wo man sagen konnte, na gut,
also da hat man also irgendwie so die ersten Prozessoren dann also gehabt,
die man dann, also du bist auf 4,
5 Gigahertz letztendlich hochzwirbeln konnte, wenn ich ihn damit anhergehend,
dann aber eben auch entsprechend immer länger werdende oder immer größer werdende Pipelines,
inzwischen ist der Trend bei den Prozessoren wieder eher so ein bisschen so,
dass man sagt, eben gerade jetzt also aus energietechnischen Gründen
Ich mache jetzt also nicht mehr nur allerhöchste Taktfrequenzen und bei den aktuellen Prozessoren ist die Anzahl der Pipeline-Stufen jetzt,
glaube ich, auch wieder ein bisschen,
also sprich bei den aktuellen Co-I-Prozessoren war von Intel beispielsweise jetzt also auch wieder kleiner geworden,
als 20. Gut, wenn man das jetzt nochmal so ein bisschen zusammenfasst,
wie wirken sich jetzt eigentlich irgendwelche Entscheidungen hinsichtlich der Prozessorarchitektur,
beziehungsweise des Befehlssatzes auf das Pipelining aus, dann kann man sagen,
okay, also letztendlich gut für Pipelining ist gleiche Instruktionslänge,
dass sich eben wirklich sowas wie ein Risk-Befehlssatz habe und eben sagen kann,
mit jedem Tag hole ich mir einen neuen Befehl,
wenn ich einen Cisk-Befehlssatz habe, ich weiß,
dass ich ja nun beispielsweise in den Assembler-Beispielen auch aufgetaucht ist,
worüber man gesehen hat, na gut,
es gibt also irgendwelche X86-Befehle,
die belegen
Ein einziges Byte und es gibt X86 Befehle, die belegen dann also,
was weiß ich, 30 Byte oder so. Dann kann man sagen,
also allein schon, um zu sagen, Befehl holen,
Befehl, dekodieren, da ist einfach so viel Diskrepanz dazwischen,
wenn alle Befehle gleich lang sind, gleich schnell geholt werden können,
kann ich dann überhaupt erst nur sowas wie Pipelining umsetzen.
Nicht dann entsprechend wenig Instruktionsformate,
wenig unterschiedliche Instruktionsformate und eben so eine reine Load-Store-Architektur,
wenn man das Ganze dann eben wirklich inhaltlich zusammennimmt,
dann ist man eben bei riskrechnern, nicht hier als Beispiel,
Befehlsformate von Milps-Maschinen, also da früher noch als 32-Bit-Rechner,
wo ich dann eben sagen kann,
hierfür Festkomma-Berechnung habe ich eben im Prinzip nur drei unterschiedliche Instruktionsformate,
nämlich also irgendwie sowas, wo ich irgendwelche Registerberechnungen mache,
dann habe ich hier ein Instruktionsformat für unbedingte Sprünge,
wo ich also jetzt ein Jahr von Running Emediate-Wert eine Adresse drin stehen habe.
Und dann habe ich eben noch irgendwie eine Instruktionsformat,
wo ich dann also im Prinzip jetzt mit Quelle,
Ziel und eben einen kleinen, emiliert Wert rechne.
Und das war es dann aber auch. Das sind eben
wirklich vergleichsweise sehr einheitliche Seven, sehr wenige Formate.
Und da werden dann also wirklich alle Befehle sein,
dass Alu-Befehle,
sahen das Sprünge
Bedingte Sprünge letztendlich untergebracht. Gut, bei den Fließkomma-Formaten eben auch nur entsprechend wenig unterschiedliche. Gut,
das führt dann eben dazu, dass man sagen kann,
wenn ich erst mal keine Datenabhängigkeiten habe und jetzt eben hier,
also was weiß ich, so ein Load-Subtraktion, Addition,
noch mal ein Load, hier wieder eine Addition habe,
dann also hier irgendwie da rein nach,
dass jetzt also irgendwie in meine Pipeline reinschiebe,
dass das dann eben wirklich so möglich ist,
dass dann beispielsweise jetzt hier also bei diesen fünf Befehlen dann
also hier in der Mitte dann eben auch wirklich alle Befehle gleichzeitig in der Pipeline in der Abarbeitung sind.
Nicht der letzte Befehl wird hier quasi gerade geholt.
Der erste ist währenddessen dann eben gerade in der Stufe Rideback und das Ergebnis irgendeiner,
dieser Lotoperation wird dann also gerade in die Registerbank wieder zurückgeschrieben
in das Register 10 in diesem Fall,
was also hier dies Dollar 10 ist
Gut, hört sich jetzt natürlich erstmal ganz gut an.
Jetzt gibt es aber eben mehrere Effekte,
die dafür sorgen, dass Pipelining eben nicht so optimal funktioniert.
Und zwar gibt es da dann eben etwas,
was man dann quasi in Anlehnung zu den Effekten,
jetzt bei Schaltennetzen, dann eben auch immer als Hazard bezeichnet.
Einmal Strukturkonflikte, Datenkonflikte und Steuerkonflikte als Dritte ist.
Strukturkonflikte sehen im Prinzip so aus, dass man sagt,
ich habe hier einfach mehrere Stufen,
die gleichzeitig auf eine Ressource zugreifen wollen und wo das eben genau der Fall ist,
ist beim gleichzeitigen Speicherzugriff.
Sprich, diese Situation Ich habe hier als ersten Befehl ein Load.
Das heißt also, hier wird eine Speicheradresse berechnet.
Wird dann also quasi hier in einem vierten oder in dem dritten Tag
dann also entsprechend, ist die Adresse berechnet worden,
wird dann also aus dem Speicher was gelesen.
Das Ergebnis landet dann hier in diesem Pipeline-Register und wird dann eben
einen Tag später in die Registerbank in das Register 10 zurückgeschrieben.
Jetzt sind hier irgendwelche anderen Befehle. So,
und wenn ich jetzt hier im Prinzip bei dem vierten weiteren Befehl bin,
habe ich nämlich genau das Problem, dass ich hier in dieser Grafik,
anders als bei einer anderen Grafik,
vorher die Speicher immer alle Mem genannt habe für Memory.
Das heißt, wenn ich jetzt also nicht getrennt Instruktions- und Datenspeicher habe,
habe ich also spätestens nach vier Befehlen oder vier Befehle nach so einem Lot,
dass ich also hier lesend auf den Speicher zugreifen möchte und hier
Vier Befehle in dem Fall jetzt, also diesem Befehl,
auch lesen möchte, der dann zwar im Instruktionsspeicher ist,
nicht während dieses hier in Zugriff auf den Datenspeicher ist,
aber wenn ich jetzt wirklich nur einen einzigen Speicher habe,
genau dann habe ich eben so einen Strukturkonflikt.
Das heißt, das ist genau der Grund,
weswegen man für das Pipelining inherent sowieso erstmal getrennte Instruktions- und Datenspeicher braucht,
weil das Ganze sonst nicht funktionieren kann
Gut, einfachste Lösung dafür eben,
Mehrfachauslegungen der entsprechenden Ressource und das bekannteste davon ist dann etwas,
was sich Harvard-Architektur nennt. Nämlich, dass man,
wie das dann also in den anderen Grafiken auch immer schon aufgetaucht ist,
also wirklich getrennte Instruktions- und Datenspeicher hat.
Im Gegensatz zu dem klassischen von Neumann-Rechner,
nachher sieht es nur um das Stichwort,
hier nochmal reinzubringen,
das Ganze nennt sich dann Princeton Architektur,
wo es
nur einen einzigen gemeinsamen Speicher geht
Gut, wie man den Namen entnehmen kann,
haben sich da dann völlig in den,
nicht damals,
wobei das also schon ein bisschen älter ist,
das ist dann also letztendlich auch aus den 70er Jahren die Diskussion da dann,
also die entsprechenden amerikanischen Elite-Universitäten gegenseitig,
da jetzt also bei der Computerarchitektur da irgendwie akademisch behakt und
jeder hat dann also quasi einen Namen mit abbekommen
Gut, andere Möglichkeit wäre, wenn ich einen Speicher habe,
wo ich gleichzeitig mehrere Adressen anlegen kann und schreiben beziehungsweise lesen kann,
also sprich irgendwie sowas wie so eine Art Multiport-Speicherarchitektur,
also auch das würde in diesem Fall dann so einen Strukturkonflikt direkt vermeiden.
Gleiches gilt eben dann auch für andere Ressourcen,
also irgendwelche Bussysteme oder irgendwelche Multiplexer,
da lassen sich dann eben immer noch je nach Befehlssatz und Architektur,
also entsprechende Beispiele finden, aber klassisches Beispiel ist eigentlich eben immer dieser Speicherzugriff.
Gut, dann das, was man eigentlich typischerweise an Konflikten hat,
sind dann eben Datenkonflikte. Data Hazard. Sprich,
eine Instruktion braucht irgendwie das Ergebnis einer vorhergehenden Instruktion.
Die ist aber noch in der Pipeline selber in Bearbeitung. So,
was kann dort jetzt eigentlich passieren, wenn ich sage,
ich habe jetzt also irgendwie zwei aufeinanderfolgende Befehle Und ich möchte,
also in der Instruction Decode-Phase, jetzt von dem zweiten Befehl,
also irgendwas aus der Registerbank laden,
was der erste Befehl jetzt aber quasi in seinem Ride-Back erst zurückschreiben würde.
Nicht, also sprich diese Situation. Würde man jetzt natürlich erstmal annehmen,
dass das ein typischer Konflikt ist. Stimmt nur bedingt,
weil das richtige Ergebnis ist schon in der Pipeline drin,
das ist schon vorhanden, dass es nur noch nicht zurückgeschrieben,
sieht man hier in diesem Fall, ich habe hier Subtraktionsbefehl,
also sprich Inhalt von Register 1,
Minusinhalt von Register 3 und das Ganze soll in Register 2 landen.
Und hier habe ich jetzt also ein Bitvises und Register 2
mit Register 5 für unten und das Ergebnis soll in Register 12 landen.
So kritisch wird das Ganze jetzt ja an der Stelle,
wo ich jetzt sage, dieser zweite Befehl ist jetzt in seiner Operance Fatch-Phase,
das heißt also hier in einem zweiten Tag,
wo ich also hier aus dem Register was lesen möchte.
Das Register 2 in diesem Befehl wird aber erst hier hinten zwei Takte später geschrieben.
So, jetzt kann man natürlich sagen, okay,
ich muss da jetzt irgendwie zwei Takte warten oder irgendwie was anderes machen.
In dem Fall kann man das Ganze aber ja eigentlich mit so ein bisschen Trickserei schon wieder auflösen,
weil man sagen kann, wenn hier aus dem Register gelesen werden soll,
das Ergebnis Was später hier mal landet,
dann ist das in dieser Stufe vorher hier schon berechnet,
weil diese Stufe ist, während ich an dieser Stelle,
wo ich also quasi jetzt hier diesen Operan Fetch mache,
schon in der Execute-Phase.
Das heißt, das Ergebnis steht schon am Ausgang der Alu und in diesem Pipeline-Register drin. Das heißt,
wenn man das Ganze jetzt so umbiegen kann, dass man sagt,
ich nehme jetzt nicht Hier an dieser Stelle das Ergebnis aus der Registerbank,
sondern ich nehme an dieser Stelle das Ergebnis hier aus diesem Pipeline-Register und rechne damit weiter,
weil das wird ja erst einen Tag später benötigt,
würde das Ganze hier ohne Datenkonflikt abgehen.
Also hier sind noch diverse andere Beispiele,
wo also auch immer sowas auftaucht,
was dann immer durch diese roten Pfeile gekennzeichnet ist und eben nur in dem Fall,
wo ich jetzt hier so einen grünen Fall habe,
das heißt,
hier wurde das Ergebnis geschrieben,
hier wird damit weitergerechnet,
habe ich da also letztendlich
keinerlei Probleme
So und dieses Auflösen von diesen Abhängigkeiten, die jetzt also,
wo die Werte vielleicht schon irgendwo in der Pipeline drin stehen,
aber direkt woanders dann schon weiter benutzt werden müssen,
kann man durch etwas lösen, was sich dann Forwarding nennt.
Das heißt, da gibt es dann also entsprechende Hardware-Einheiten,
die letztendlich buchführen,
welche Datenabhängigkeiten sind vorhanden und eben über entsprechende Multiplexer-Logiken dafür sorgen,
dass dann eben Wenn jetzt also der nächste Befehl sagt,
ich möchte hier Inhalt aus Register 2 lesen,
dieser Inhalt eben nicht aus Register 2 kommt,
sondern wenn eben vorher ein Befehl steht,
der in Register 2 schreibt,
das Ergebnis hier direkt aus dieser Pipeline-Stufe Ausgang der Alu
dann eben entsprechend zurückgeführt wird
Nicht, da muss an dem zufolge dann entsprechend neue Daten fahren und
Multiplexe anlegen und das Ganze in der Pipeline noch mit einbauen,
ist im Prinzip dann hier in dieser Grafik verdeutlicht.
Das heißt, hier habe ich jetzt also diesen Teil Registerbank,
Alu, Register am Ausgang der Alu, Speicherzugriff und das Register.
Und wenn man hier jetzt eben die Möglichkeiten schafft,
hier an die Eingänge der Alu über entsprechende Multiplexer den Inhalt
dieses Registers und den Inhalt dieses Registers auch noch mit reinzuführen und eben nicht nur aus der Registerbank,
also sprich aus diesen beiden Eingängen zu lesen, dann kann man eben solche Datenkonflikte letztendlich beseitigen.
Gut, das ist der einfache Fall. Es gibt dann aber eben auch noch etwas,
wo das leider nicht mehr funktioniert,
nämlich wenn es sich um sogenannte Rückwärtsabhängigkeiten handelt,
wo die Daten dann eben wirklich erst später zur Verfügung stehen,
was beispielsweise bei Lotbefehlen der Fall ist.
Also jetzt hier in diesem Beispiel oder wenn ich längere Pipelines habe,
kriege ich das also auch bei anderen Befehlen mit hin.
Sieht man also hier in dieser Grafik. Ich habe hier
ein Lotbefehl, ist so etwas aus dem Speicher gelesen werden.
Ich würde demzufolge hier irgendwie die Speicheradresse berechnet. Ich habe da
noch nie hier in Zukunft auf das DM steht dann immer für Data Memory,
habe also hinterher entsprechend hier nach vier Takten das Ergebnis in diesem Pipeline-Register,
was dann also quasi ein in der nächsten Stufe dann eben in die Registerbank zurückgeschrieben würde.
Wenn jetzt aber eben darauf folgenden Takt schon mit diesem Ergebnis gerechnet wird,
spreche ich dann also hier den Zugriff haben möchte,
dann sieht man,
dass das eben nicht mehr durch ein Forwarding aufgelöst werden kann,
weil da eben wirklich
die zeitliche Abhängigkeit zurückverweist
Nicht, wenn das jetzt hier nur ein Tag wäre,
würde das gehen, in dem Fall aber nicht.
Was kann man dort machen? Zum einen kann der Compiler,
der letztendlich den Assembler-Code erzeugt, versuchen,
ihre Reihenfolge der Instruktion zu ändern, um eben solche Rückwärtsabhängigkeiten aufzulösen.
Das heißt also irgendwelche Operationen,
wo ich jetzt vielleicht keine Datenabhängigkeiten habe,
werden,
einfach weiter vorne irgendwie dazwischen rein sortiert
Dann kann er natürlich ganz stumpf einfach an entsprechender Stelle sowas wie so ein No-Op-Befehl einführen,
was dann dazu führt, dass,
wenn ich ja sehe,
hier genau die gleiche Situation habe,
hier noch ein zusätzliches Snob drin,
dass dann quasi das Ergebnis hier schon über ein normales Forwarding hier an die Alu weitergegeben werden kann und damit dann eben weitergerechnet werden kann.
Nicht, dass also die Möglichkeit, wie der Compiler das machen kann.
Dann gibt es noch die Möglichkeit, das Wasser quasi jetzt über eine Hardware-Einheit passiert.
Das nennt sich dann Interlocking. Das ist dann vergleichsweise komplexes Steuerwerk,
was dann also in der Regel mit diesem Scoreboard zusammenarbeitet,
wo automatisch die Pipeline oder Pipeline-Stufen eben quasi angehalten werden und dort nichts weiter reingetan wird.
Das Ganze findet man in der Literatur dann eben immer auch als Pipeline-Lehrlauf oder Pipeline-Stall,
muss man sich eben so vorstellen, wie das,
was also jetzt hier mit dieser blauen Blase quasi skizziert ist,
dass man eben sagt, gut,
an dieser einen Stelle wird eben hier diese Abhängigkeit erkannt.
Es wird dann also kein weiterer Befehl in die Pipeline reingeschoben und
dieser Endbefehl wird quasi dann nochmal um einen Tag entsprechend nach hinten gezogen,
womit dann eben auch wieder erreicht wird,
dass also hier nach dem Zugriff auf den Datenspeicher mittels Forwarding
das Ergebnis dann eben hier weitergegeben werden kann
Das sind also die Möglichkeiten, wie man sowas machen kann. Wie gesagt,
also in der Locking beziehungsweise Scoreboard, das sind dann eben irgendwelche Hardware-Einheiten,
nicht wenn man da jetzt wieder mal zurückspringen,
also relativ komplexe, endliche Automaten,
wo ich dann im Prinzip sage, abhängig davon,
welche Befehle ich jetzt hier in meine Pipeline reingeschoben habe,
verändern die dann ihre internen Zustände,
führen da quasi Protokoll mit, welche Datenabhängigkeiten vorhanden sind,
wie die aufgelöst werden können und gegebenenfalls sorgen die dann eben dafür,
dass eben hier Teile der Pipeline zwischenzeitlich anhalten.
Und gerade bei aktuellem Prozessor, Architekturen, das würde also ohne sowas alles nicht funktionieren. Und gerade,
wenn man jetzt also noch zusätzlich etwas hat, was dann,
also quasi jetzt im nächsten Kapitel mit rankommt, nämlich Superskalarrechner,
wo ich dann also auch noch mehrere Recheneinheiten habe und aufgrund der Tatsache,
dass sie jetzt also mit diesen mehreren Rechnereinheiten gleichzeitig auch noch mehrere Befehle in der Abarbeitung habe,
dann natürlich solche Situationen mit Datenabhängigkeiten noch mal deutlich komplizierter werden können.
Gut, und als letzte Art von Pipeline Hazards
habe ich dann eben sowas wie Steuerkonflikte, Control Hazard.
Letztendlich die Unterbrechung irgendwie des sequenziellen Ablaufs.
Das kann eben sein durch Sprungbefehle, Unterprogrammaufrufe. Weil,
was ich bei Pipelining ja quasi mache oder machen würde,
wenn ich jetzt immer da rein nach die Befehle in die Pipeline reinschiebe,
die Instruktionen, die auf diesen Sprungbefehl folgen,
werden immer schon brav in die Pipeline reingetan.
Aber erst am Ende der Execute-Phase,
wenn ich also beispielsweise weiß, ist das Ergebnis größer Null.
Und dann soll gesprungen werden
Weiß ich, ob das ein Sprung ist,
also sprich, als ob der Taken oder Untaken ist.
Ich habe aber schon irgendwie ein paar weitere Befehle in der
Pipeline drin, die jetzt vielleicht gar nicht ausgeführt werden.
Nach dem zufolge muss man dann alles, was dann als Zwischenergebnissen
aufgetaucht ist, gegebenenfalls wieder zurücknehmen, die Pipeline lehren.
Und dann Sprung ausführen und anschließend die Pipeline dann eben auch wieder über mehrere Takte,
entsprechend neu füllen, was dann eben dazu führt,
dass das Ganze dann eben auch entsprechend viel Aufwand kostet und letztendlich jeder Sprung entsprechend viel Performance kostet.
Frage? Wir sind uns mal vermöglicht in der Septore,
wir haben Pats und der Gerichtspunktur,
was mache ich jetzt von Neumann, möchte ich erwarten,
dass ich das noch mal erreichen kann,
also vergabt ist praktischbeziehlich dafür,
Designforderungen mit einer Leiterin
Ja, also Frage war jetzt,
also wenn es diese ganzen Probleme bei dem Pipelining gibt und gerade also hier bei Sprüngen,
wo ich jetzt eben wirklich nichts machen kann,
also das ist jetzt also hier in dieser Grafik angedeutet,
wo ich dann sage,
hier so ein Branch Equal und ich schiebe hier jetzt also die dem Sprung folgenden Befehle brav in die Pipeline und erst wenn ich also quasi jetzt hier vier Tage weiter bin,
weiß ich, es soll gesprungen werden,
habe dann aber hier schon drei Befehle in der Pipeline drin und dann musst du ja auf einmal gesprungen werden,
alles hier was, das partiell schon drin ist,
muss wieder ungültig gemacht werden.
Frage war jetzt, ja gut, warum macht man das,
also nichts oder keine anderen Konzepte,
wo man jetzt also genau solche Probleme vermeidet
Das Problem ist halt einfach, ich sag mal,
die Informatik oder das Programm lebt davon, dass ich verzweigen kann.
Also ohne IF-Anweisung, ohne irgendwelche Vorschleifen, ohne irgendeine Art von Verzweigung,
wie auch immer das letztendlich realisiert sein mag,
unter Programmaufrufe, denke ich mal,
kriegt man keinen Algorithmus irgendwie sinnvoll beschrieben.
Und deswegen stellt sie das Problem da, glaube ich,
nicht.
Noch eine Frage
Die Frage war, hatte ich es? Ne,
denke ich, denke ich jetzt nicht, weil also letztendlich das,
was eine GPU macht, ist ja eigentlich auch nur Normales von Neumann-Konzept.
Also letztendlich würde ich ja mal sagen, gut,
also GPU lebt ja dann eigentlich auch davon,
dass ich sage, ich habe da also meine relativ kleinen Programmabläufe,
also sprich das, was so typisch ist,
irgendwie so Schäderkerne oder sowas, implementiert,
wo ich aber dann im Prinzip auch ganz normal algorithmisch jetzt irgendwie was beschreibe,
was dann drauf abläuft.
Und das Einzige, was das jetzt natürlich noch erstmal hier von sowas unterscheidet,
ist, dass ich sage, also zum einen habe ich da also speziell beziehungsweise dann fast ausschließlich, also nur Fließkomma-Recheneinheiten.
Und ich habe dann also auch noch einen massiv-parallelen Rechner,
wo ich dann dementsprechend viele habe, nicht wo man ja eben sagt,
das ist ja eigentlich die ursprüngliche Anwendung,
dass man eben sagt, na gut,
wenn ich jetzt also erst mal nur irgendeine künstliche Landschaft generieren will oder irgendein Bild berechnen möchte,
dann kann ich eben sagen,
also die Berechnung in dieser Ecke ist im Großen und Ganzen erst mal unabhängig von der Berechnung in dieser Ecke.
Und ich muss dann eben nur dafür sorgen,
dass also die Teilprogramme für die Berechnung in die entsprechenden Rechenkerne kommen und jeder kann dann aber nachher,
wenn er also der beispielsweise dann,
wenn ich jetzt bei so einer Programmierung bin,
da zum Beispiel jetzt irgendwelche Texturen hat,
also dann eben darauf direkt rumrechnen und machen und solange ich jetzt nicht wirklich Abhängigkeiten dazwischen habe,
funktioniert das hervorragend und ich kann das gut auslasten.
Das ist ja eben die Sache, wo man sagt,
das macht eben sowas wie so eine GPU-Berechnung jetzt eigentlich
auch für diese ganzen High-Performance-Computing-Sachen interessant,
wo man sagt nicht,
also irgendwelche Matrizenberechnungen kann ich
entsprechend partitionieren
Teilweise dann dort direkt dort implementieren, nicht in eben irgendwelche Sachen,
wo ich dann eben so diese ganzen finite Elemente-Methoden mache,
kann man dann eben auch versuchen, da drauf zu bringen.
Aber letztendlich würde ich auch immer noch sagen,
also auch in einer GPU, also in der Grafikkarte,
habe ich irgendwo immer noch so ein Konzept,
wo ich jetzt sage,
ich musste algorithmisch immer noch irgendwie so ein bisschen
was verzweigen oder irgendwas entscheiden und machen
Was so ein bisschen von in die Richtung Pipe-Lining geht,
sind eigentlich dann eher noch andere Arten von Architekturen,
nämlich also Sachen, wo es um irgendwelche Datenflussberechnungen geht.
Wenn ich jetzt also beispielsweise sowas mache, wie Signalverarbeitung,
also sprich, ich habe ein, zwei,
drei endimensionales Signal, habe jetzt also irgendwie sowas zum Beispiel,
bleiben wir im Eindimensionalen, also irgendeine digitale Filterbank.
Da muss ich also, nehme ich die Samples und die
laufen in der Reihe nach immer nur in Multiplikation, Addition, Multiplikation,
Addition entsprechend irgendwie durch und das kann ich hervorragend in so einer Pipeline unterbringen.
Das sind aber Sachen, wo ich dann also eigentlich,
anders als jetzt in einer normalen Programmierung,
wo ich also im Prinzip dann eher so einen Kontrollflussorientiertes Paradigma habe,
dann also etwas datenflussorientiert ist.
Aber wie gesagt, also hier sind wir erstmal noch bei
den ganz einfachen Kontrollflussorientierten Konzepten. Gut,
das war jetzt nochmal so ein bisschen... Erbsen zurück,
also wie gesagt, Steuerkonflikte, das ist eben letztendlich dann das Problem mit den Sprüngen.
Auch da kann man sagen, gut, wie kann ich dem begegnen?
Zum einen kann man jetzt natürlich sowas wie so ein Interlocking oder irgendwelchen No-Ops einführen,
die dann dafür sorgen, dass die Pipeline letztendlich bei irgendwelchen Sprüngen,
also sprich bei den Befehlen, die jetzt nachsprüngen, folgen,
eben entweder künstliche No-Ops eingeführt werden oder dann über so ein Interlocking angehalten werden.
Macht natürlich niemand, weil das vergleichsweise ineffizient ist und eben,
wie gesagt, so laut dieser Statistik,
wenn man also da den generierten Code sich immer ansieht,
eben vergleichsweise große Anteil der Befehle
wirklich Sprünge sind
Das, was man eigentlich macht und was also extrem gut eigentlich funktioniert,
sind eher andere Herangehensweisen, nämlich zum einen kann ich erstmal sagen,
man baut sowas wie irgendwelche impliziten Annahmen mit ein.
Also was für eine Programmierung häufig hat, ist eine Vorschleife.
Und da weiß ich, Vorschleife, da wird erstmal in mal durchiteriert
Und en mal mache ich genau die eine einzige Bedingung,
wo ich eben immer zurückspringe und nur ein einziges Mal mache ich davon was Abweichendes.
Das heißt, man macht dann eben irgendwelche impliziten Annahmen,
sei das entweder ist ein Sprung nicht stattfindet,
an Taken Branch oder ist ein Sprung stattfindet Taken Branch,
rechnet dann quasi mit diesem häufigeren Fall und hat dann eben nur ein einziges Mal den,
in Anführungszeichen, nicht einmal daneben gegriffen, den Fall,
der dann eben noch ein bisschen Zusatzaufwand kostet.
Das funktioniert eigentlich
ganz gut
Dann hat der Compiler die Möglichkeit, ähnlich wie bei Datenabhängigkeit,
teilweise Befehle so ein bisschen umzusortieren. Das heißt,
da kann man dann eben irgendwelche Befehle, Nachsprungbefehlen reinführen,
die dir jetzt also ohne Abhängigkeiten sind,
wo ich dann eben auch entsprechend nicht irgendwas in der Pipeline anfange,
was ich also gegebenenfalls umständlich wieder zurücksetzen muss.
Und dann gibt es eben auch noch die Möglichkeit,
über entsprechende Zusatz-Hardware, also beispielsweise irgendwelche Alus dafür zu sorgen,
dass eben nicht erst nach vier Takten bekannt ist,
es soll gesprungen werden, sondern eben schon deutlich früher.
Und dadurch dann entsprechend das Ganze beschleunigt wird. Nochmal eine Frage,
eine gute Fälle gibt es, dass man sagt,
dass ein Wahnsinn ist oder nicht, ist das bleibt,
das feiert sich,
der ist von seiner Führungsgesichter
Das ist eines dieser komplizierten Gesellschaften, weil es ist eine praktische Situation,
wo eine Meinung geheilt ist, die führt einen Platz aus einer Bedingung da, so der andere. Ja,
das ist im Prinzip so und nicht,
also sagen wir mal, das sind ja also die,
die programmiertechnische Konstrukte, die man da in der Regel benutzt,
ein Compiler wird das eben dann auch immer so umsetzen,
dass quasi der, in Anführungszeichen, normale Fall,
also sprich der Schleifendurchlauf, dann eben hier in diesen Fall fällt,
wo ich keinen Pipeline-Konflikt habe und ihm nur ein einziges Mal ich dann eben diesen falschen Fall habe.
Aber dass wir dann eben auch aus dem Beispiel... Gut,
und das andere ist, dass es dann eben noch etwas gibt,
was also Sprungvorhersage ist, also Branch-Prediction, Prediction,
wo man dann eben nicht nur sagt, okay,
also der eine Fall tritt häufiger aus,
sondern man verbindet dann eben noch verschiedene Verfahren,
um dann eben auch wirklich zu sagen, es wird gesprungen,
es wird nicht gesprungen.
Und das funktioniert also
erstaunlich gut
Passiert zum einen Software-basiert als statische Sprungvorhersage,
wo also im Prinzip der Komparle jetzt noch ein bisschen zusätzlichen Quote mit einbaut und
dann also quasi über Tabellen mit protokolliert wird, wird gesprungen,
wird nicht gesprungen und also aufgrund dieser Tabellen das Ganze verfahren dann arbeitet.
Und dann gibt es eben noch dynamische Sprungvorhersagen,
wo dann also im Prinzip Hardware basiert,
also sprich dann auch über irgendwelche Automaten und Zählermechanismen
Dort geguckt wird zur Laufzeit, wie häufig wurden Sprünge ausgeführt oder nicht. Da gibt es also,
wenn man in die Literatur guckt und jeder Prozessor Hersteller überbietet sich da jetzt,
also irgendwie mit seinen Verfahren und Algorithmen,
also relativ viele Sachen, die dann also im Prinzip hier,
also nur so ein paar Stichworte aufgezählt,
die dann im Prinzip sowas letztendlich versuchen.
Gut, eine andere Möglichkeit, etwas,
was dann also auch immer noch gemacht wird,
ist dann das sogenannte Loop Unrolling, wenn ich statische Schleifen habe,
also jetzt nicht irgendwelche Wahlschleifen oder irgendwelche Anthilschlafen,
sondern sowas wie eine Vorschleife, dann weiß ich quasi vorher,
wie oft die iteriert wird.
Und das, was der Compiler dann nämlich machen kann,
ist, dass er einfach den Inhalt der Schleife nimmt,
entsprechend oft in den kompilierten Assembler-Code
untereinander parkt
Dann kann er nämlich auch noch auf die abfragen und auf
die Sprünge verzichten und muss dann eben in dem Code nur das,
was also beispielsweise als Schleifenindex benutzt wird,
natürlich entsprechend anpassen,
um dann quasi in der ersten Iteration mit I und in der nächsten Iteration mit iPlus 1 zu rechnen.
Nicht aber das wird dann eben auch entsprechend gemacht und dadurch
kann dann eben auch die Pipeline entsprechend gut ausgenutzt werden.
Gut, das war jetzt nämlich also auch
schon die letzte Folie zum Thema Pipelining.
Hier nochmal kurz die Zusammenfassung zum Nachlesen und damit werde ich
dann nämlich vor Ort auch Schluss machen und am Freitag
dann noch ein bisschen was zu anderen Arten der Parallelverarbeitung sagen