CRAY-1 - Weblearn - Hochschule Bremen

Werbung
Referat
Vektorisierung auf pipeline-Rechnern
am Beispiel der CRAY Y-MP
von
Markus Wegner
und
Michael Stange
Hochschule Bremen
RST-Labor
Semester I7I1
Samstag, 14. Mai 2016
Markus Wegner, Michael Stange, I7I1
14.05.2016
Inhaltsverzeichnis
Warum Vektorisierung? __________________________________________3
Geschichtlicher Überblick ________________________________________4
CRAY-1 ______________________________________________________________________ 4
CRAY-2 ______________________________________________________________________ 5
CRAY-XMP ___________________________________________________________________ 5
CRAY-YMP ___________________________________________________________________ 6
Architektur der CRAY-YMP 816 __________________________________6
Probleme bei der Vektorisierung ___________________________________7
Dependency Tests _______________________________________________9
Dependencies beheben __________________________________________10
Umordnen von Anweisungen ____________________________________________________ 10
Recurrence Threshold __________________________________________________________ 10
Save Vector Length ____________________________________________________________ 11
Loop Unrolling ________________________________________________________________ 11
Partielle Vektorisierung ________________________________________________________ 12
Bedingte Vektorisierung ________________________________________________________ 12
Loop Switching________________________________________________________________ 12
Matrix Multiplikation __________________________________________________________ 13
Performance Gewinn durch Vektorisierung (Amdahls Gesetz) _________14
Speichersysteme________________________________________________15
Realer Speicher _______________________________________________________________ 15
Memory Banks ________________________________________________________________ 17
Quellen _______________________________________________________18
__________________________________________________________________________________________
Seite 2 von 18
Markus Wegner, Michael Stange, I7I1
14.05.2016
Warum Vektorisierung?
Bisher sind wir davon ausgegangen, daß wenige Eingabegrößen (Operanden) vorliegen, die schon zu Beginn des
Programmablaufs abrufbereit in Registern bereitstehen. Wir hatten also das Laden der Operanden aus dem
Speicher vollständig außer acht gelassen. Die Wichtigkeit dieses Problems illustriert das folgende
Programmfragment:
10
DO 10 I = 1, 63
A(I) = B(I) + C(I)
CONTINUE
Wollte man hier für die Addition Pipelining ausnutzen, müßten vorher alle 126 B() und C() –Werte in Register
geladen werden. Die CRAY besitzt aber nicht so viele Einzelregister zum Speichern von jeweils einer Zahl.
Dafür besitzt sie Vektorregister, denen sie letztendlich den Namen Vektorrechner verdankt. In einem
Vektorregister können mehrere Zahlen gleichzeitig gespeichert und direkt verarbeitet werden. Sie wurden gerade
für die Verarbeitung solcher Schleifen konstruiert. Die genaue Funktionsweise wird am besten bei Betrachtung
eines Beispiels deutlich (Abb. 1):
Abb.1: Zeitablauf für die Addition zweier Vektoren
Zuerst wird das gesamte Feld A() aus dem Speicher in ein Vektorregister geladen. Ein Vektorregister der CRAY
kann 64 Elemente (Zahlen der Länge 8Byte) aufnehmen. Hierfür benötigt sie 17 Zyklen. Parallel dazu kann sie
auch Feld B() laden, allerdings kann sie erst 3 Zyklen später beginnen, da diese Zeit benötigt wird, um die
Startadresse von B() zu laden. Nach 20 Zyklen stehen die Zahlen bereit und werden an die „+“- Pipeline
übergeben, die ja 6-stufig ist. Pipelining wird hier optimal genutzt, da die Vektorregister in jedem weiteren
Taktzyklus je einen Operanden übertragen. Nach 26 Zyklen steht das erste Ergebnis bereit; zum Speichern
werden weitere 3 Zyklen benötigt. Das erste Ergebnis verläßt also nach 3 + 17 + 6 + 3 = 29 Taktzyklen die CPU.
Diese Überlegung stellt die Zusammenhänge vereinfacht dar, wie wir später noch sehen werden. In Abb.1 sind
die Lade- und Speicherbefehle in der selben Weise gezeichnet wie Functional Units, was sie ja eigentlich nicht
sind. Die Graphik soll nur den Zeitablauf verdeutlichen. Die Ladezeit von 17 Zyklen bedeutet, daß nach 17
Zyklen der erste Operand im Vektorregister zur Verfügung steht. In jedem weiteren Zyklus wird dann ein
weiterer Operand geladen. Das Speichern geht schneller, weil es- aus der Sicht des Vektorregisters- reicht zu
warten, bis der Operand das Register in Richtung Speicher verlassen hat. Zu diesem Zeitpunkt ist das
Vektorregister wieder frei, obwohl der Operand noch nicht wieder im Speicher ist. Erst nach 17 Zyklen liegt
dann das Ergebnis im Speicher vor. Wenn die gesamte Maschinerie, bestehend aus Speicherzugriffslogik,
Vektorregistern und Functional Unit einmal in Gang gesetzt ist, „fließen“ die Daten so durch das System, daß in
jedem Zyklus ein Schleifendurchlauf abgearbeitet wird. Erst durch die Vektorregister wird es möglich, die
Functional Units so schnell mit Eingabedaten zu beliefern, daß Pipelining genutzt werden kann. Dies erfordert
__________________________________________________________________________________________
Seite 3 von 18
Markus Wegner, Michael Stange, I7I1
14.05.2016
wiederum, daß auch der Speicher in der Lage ist, die Operanden schnell genug an die Vektorregister
weiterzugeben und die Ergebnisse entsprechend entgegen zu nehmen. In unserem Beispiel müssen in jedem
Taktzyklus 3*8=24 Bytes bewegt werden.
Geschichtlicher Überblick
CRAY-1
Der CRAY Vektorrechner wurde Ende der 70er Jahre von Seymour CRAY konzipiert und verwirklicht.
Abb.2: CRAY-1 Vektorrechner
Als erste Vektor- Architektur mit Vektorregistern erschien die CRAY-1 auf dem Markt. Das vereinfachte
Blockschaltbild der CRAY-1 ist in der Abb.2 dargestellt. Der Hauptspeicher ist in 16 Speicherbänke aufgeteilt
mit einem Speicherzyklus von 50 ns (Maschienenzyklus 12 ns). Dieser Vektorrechner enthält 8 Vektorregister
und 16 Mehrzweckregister, von denen jeweils 8 für die Abspeicherung von Daten (Skalare) und 8 für die von
Adressen vorgesehen sind. Anstelle von Caches sind Puffer zwischen Mehrzweckregister und Hauptspeicher
eingesetzt. Die Puffer sorgen für eine schnelle Nachlieferung der Adressen und Daten zu den
Funktionseinheiten. Der Befehlspuffer realisiert das Prinzip Prefetch- Buffers.
Ein schwieriges Problem der CRAY-1 liegt in der Verarbeitung von Vektoren mit einer Länge größer als 64
Elemente. Um Operationen mit größeren Vektoren von z.B. 4096 Elementen auszuführen, müssen diese in
Teilstücke von 64 Elementen zerlegt werden. Diese Fragmente müssen dann stückweise verarbeitet werden. Eine
__________________________________________________________________________________________
Seite 4 von 18
Markus Wegner, Michael Stange, I7I1
14.05.2016
solche Aufteilung eines Vektors in Teilstücke, die in ein Vektorregister passen, heißt Sektionierung 1. Im
Normalfall besitzen aber die zu verarbeitenden Vektoren keine Längen mit einem Vielfachen von 64 Byte, so
daß ein Reststück der Vektoren nach der Sektionierung übrig bleibt und erfahrungsgemäß nicht am Ende,
sondern am Anfang verarbeitet werden muß.
Nachdem die erste Sektion der zu verarbeitenden überlangen Vektoren ein Ergebnis generiert hat und dieses im
Hauptspeicher abgespeichert wurde, sollte aus Performance- Gründen die Möglichkeit bestehen, die nächste
Sektion der Quell-Operanden in die betreffenden Vektorregister zu laden. Diese drei Zugriffe können teilweise
zeitlich überlappend vorgenommen werden. Die CRAY-1 kann aber innerhalb eines Maschinenzyklus nicht die
notwendigen drei Zugriffe, sondern nur einen einzigen realisieren. Infolge dieses Nachteils sinkt die theoretische
Gleitkomma- Verarbeitungsleistung von 80 MFLOPS auf 80/3 MFLOPS.
Dieser Nachteil der CRAY-1 Architektur ist in der CRAY-2 und später in den CRAY Nachfolgearchitekturen
beseitigt worden.
CRAY-2
Abb.3: Speicher-Organisation der CRAY-2-Architektur (4Speicher-Quadranten zu je 32Bänken und 1Gbyte)
Der CRAY-2 Vektorrechner wurde zum Beispiel mit einem Hauptspeicher von 512 M Worten (Wort=64Bit)
ausgeliefert. Der Hauptspeicher ist in der Form organisiert, daß die 4 Gbyte in 4 Quadranten zu je 32 Bänken
aufgeteilt werden (Abb.3). Bezüglich des ‘Stride‘ existieren unterschiedliche Möglichkeiten. Wenn die
Vektorelemente aufeinanderfolgend in der linken oberen Ecke des 1., 2., 3. und 4. Quadranten beginnend
(schraffiert) angeordnet sind, hat der ‘Stride‘ den Wert 1. Werden die Elemente in 4 benachbarten Bänken
desselben Quadranten angeordnet, beträgt der ‘Stride‘=4, während beim Abspeichern in einer Speicherbank mit
einem ‘Stride‘=128 gerechnet werden muß. Der Hauptspeicher der CRAY-2 kann optimal mit statischen (55ns
Zyklus-Zeit) oder dynamischen (80ns Zyklus-Zeit) RAM-Bausteinen ausgerüstet werden.
CRAY-XMP
Als Nachfolgearchitektur der CRAY-2 wurde die CRAY-XMP EA entwickelt. Dieser Vektorrechner enthält
insgesamt 13 separate Pipelines (4 Vektor-Festkomma-Pipes, 3 Gleitkomma-Pipes mit je 4 Stufen, 4 SkalarFestkomma-Pipes, 2 Adressen-Pipes). Der Hauptspeicher ist in 64 Speicherbänke aufgeteilt, auf die unabhängig
voneinander zugegriffen werden kann (68ns Zyklus-Zeit). Die CRAY-XMP arbeitet ohne virtuelle
Speichertechnik.
Die Vektor-Sektionierung wird im Angelsächsischen Sprachgebrauch als „strip mining“ bezeichnet, in
Anlehnung an das Abtragen von Schichten im Minen- Tagebau.
__________________________________________________________________________________________
1
Seite 5 von 18
Markus Wegner, Michael Stange, I7I1
14.05.2016
CRAY-YMP
Einen leistungsfähigenVektorrechner stellt die CRAY-YMP dar. Die wichtigste Implementierung dieser
Architektur stellen die drei sogenannten Adressen-Pipes dar, die dem Rechner ermöglichen, gleichzeitig drei
Hauptspeicher-Zugriffe (2 Lese- und 1Schreiboperation) vorzunehmen. Eine Adressen-Pipe ist eine Verbindung
zwischen zwei Crosspoint-Matrixschaltern. Für eine optimale Performance ist es jedoch notwendig, daß keine
Zugriffskonflikte zu den Hauptspeicherbänken auftreten. Die CRAY-YMP verfügt bei maximaler Ausrüstung
über 8 CPU’s (6ns Zyklus-Zeit, 4 parallele Daten-Pfade, 8*32 Bit Adressen-Register, 8*64 Bit Skalar-Register,
8*64 Bit Vektorregister zu je 64 Elementen) und 256 MByte Hauptspeicher (256 Bänke, 30ns Zyklus-Zeit). Die
maximale Gleitkommaleistung beträgt 2.67 GFLOPS (maximal 2 Operationen pro Zyklus). Die YMP C90
Architektur verfügt über 16 CPU’s mit einer Leistung von maximal 13.7 GFLOPS.
Architektur der CRAY-YMP 816
Abb.5: CRAY-YMP 816
__________________________________________________________________________________________
Seite 6 von 18
Markus Wegner, Michael Stange, I7I1
14.05.2016
Das schematische Blockschaltbild der YMP 816 wird in der Abb.5 gezeigt. Das System kann für 1, 2, 4 und 8
Prozessoren konfiguriert werden. Die 8 CPU’s der YMP teilen sich den Hauptspeicher, die I/O Einheit,
Interprozessor Kommunikation und die Echtzeituhr. Der Hauptspeicher ist in 256 überlappende Bänke unterteilt.
Ein 6 ns Taktzyklus wird in der CPU verwendet.
Der Hauptspeicher wird mit 16M-, 32M, 64M, und 128 M- Wort und einer maximalen Größe von 1 Gbyte
angeboten. Die SSD Optionen gibt es von 32 M bis 512 M Worte oder bis zu 4 Gbytes. Die 4
Speicherzugriffskanäle erlauben jeder CPU, gleichzeitig 2 Skalar- und Vektor Aufrufe, einen Speicher und einen
unabhängigen I/O Zugriff auszuführen. Diese parallelen Speicherzugriffe erfolgen auch über eine Pipeline, um
Vektor-READ und Vektor-WRITE zu ermöglichen. Die Architektur wurde so konzipiert, daß die durch
Speicherkonflikte verursachten Pausen minimiert wurden. Um die Daten zu schützen, wird die SECDED (Single
Error Correction and Double Error Detection) Logik im Hauptspeicher und an den Datenkanälen vom und zum
Hauptspeicher verwendet. Die Recheneinheit verfügt über 14 Functional Units, die unterteilt sind in Vektor,
Skalar, Adress und Control Sektionen. Beide Skalar- und Vektorinstruktionen können parallel ausgeführt
werden. 8 von den 14 Functional Units können von den Vektorinstruktionen verwendet werden.
Es werden große Zahlen von Adress, Skalar, Vektor, Intermediate und temporäre Register verwendet. Durch das
benutzen von Registern, mehrfachen Speicherzugriffen und Arithmetic Logic Pipelines wird das flexible
Chaining von Functional Pipelines ermöglicht. 64 Bit Floting Point und 64 Bit Integer Arithmetic sind
ausgeführt. Große Instruction–Buffers werden benutzt, um 512 16 Bit Instruktionspakete zur gleichen Zeit zu
halten. Die Interprocessor Kommunikations- Sektion vom Großrechner verfügt über Cluster von geteilten
Registern für schnelle Synchronisationszwecke. Jedes Cluster verfügt über geteilte Adress-, geteilte Skalar und
Semaphor- Register. Die Vektor Datenkommunikation unter den CPU’s wird durch den geteilten Speicher
realisiert. Die Echtzeituhr verfügt über einen 64 Bit Zähler, der bei jedem Taktzyklus hochgezählt wird. Weil der
Takt synchron mit der Programmausführung hochgezählt wird, kann er während der Ausführung als exakter
Zähler verwendet werden. Die I/O Sektion unterstützt 3 Kanaltypen mit Transferraten von 6 Mbyte/s, 100
Mbyte/s und 1 Gbyte/s. Die IOS und SSD sind Hochgeschwindigkeits-Module, die entwickelt wurden, um
Großrechner Prozesse für 8 Caches zu unterstützen.
Probleme bei der Vektorisierung
Die Probleme, die bei der Vektorisierung auftreten können, wollen wir an einem kleinen Beispiel illustrieren.
Hierfür betrachten wir folgendes Programm:
10
INTEGER J(3), K(3), L(3), M(3), N(3)
DATA J/ 2, -4, 7 / K/ 5, 3, 8 / M/ 4, 6, -2/
DO 10 I = 1, 3
L(I) = J(I) + K(I)
N(I) = L(I) + M(I)
CONTINUE
END
Ohne Vektorisierung:
L(1) =
N(1) =
L(2) =
N(2) =
L(3) =
N(3) =
J(1) +
L(1) +
J(2) +
L(2) +
J(3) +
L(3) +
K(1)
M(1)
K(2)
M(2)
K(3)
M(3)
7 = 2 +5
11 = 7 + 4
-1 = -4 + 3
5 = -1 + 6
15 = 7 + 8
13 = 15 + ( -2 )
__________________________________________________________________________________________
Seite 7 von 18
Markus Wegner, Michael Stange, I7I1
14.05.2016
Mit Vektorisierung:
Wenn das Programm vektorisiert abläuft, wird zuerst die gesamte Schleife für die Anweisung
L(I) = J(I) + K(I)
ausgeführt, dann für die Anweisung
N(I) = L(I) + M(I).
L(1) =
L(2) =
L(3) =
N(1) =
N(2) =
N(3) =
J(1) +
J(2) +
J(3) +
L(1) +
L(2) +
L(3) +
K(1)
K(2)
K(3)
M(1)
M(2)
M(3)
7 = 2 +5
-1 = -4 + 3
15 = 7 + 8
11 = 7 + 4
5 = -1 + 6
13 = 15 + ( -2 )
Die Reihenfolge der Anweisungen wird durch die Vektorisierung verändert. Auf das Ergebnis hatte die
Umgruppierung in diesem Beispiel keinen Einfluß, L() und N() haben nach dem Schleifendurchlauf in beiden
Fällen den gleichen Inhalt. Das ist aber nicht immer der Fall, wie folgendes Beispiel zeigt:
10
INTEGER A(3), B(3), C(3)
DATA A/ 1, 2, 3 / B/ 5, 7, 9 /
DO 10 I = 3, 2, -1
A(I) = B(I)
C(I) = A(I-1)
CONTINUE
Ohne Vektorisierung:
A(3) = B(3)
C(3) = A(2)
A(2) = B(2)
C(2) = A(1)
A(3) = 9
C(3) = 2
A(2) = 7
C(2) = 1
Mit Vektorisierung:
A(3) = B(3)
A(2) = B(2)
C(3) = A(2)
C(2) = A(1)
A(3) = 9
A(2) = 7
C(3) = 7
C(2) = 1
Es hängt also von der Konstruktion des Programms ab, ob man durch Vektorisierung zu falschen Ergebnissen
kommen kann, weil durch die Vektorisierung die Reihenfolge der Anweisungen geändert wird. Die falschen
Ergebnisse kommen durch Abhänigkeit der Daten innerhalb der Schleife zustande. Diese Abhänigkeit wird
Dependency genannt. Sie kann auftreten, wenn in einer Schleife ein Feldelement definiert und benutzt
(referenziert) wird.
Der Compiler erkennt die Gefahr von Dependencies und erzeugt in diesem Fall keinen vektorisierten Code, d.h.,
das Problem, daß durch Vektorisierung des Compilers falsche Ergebnisse erzeugt werden, besteht nicht.
Das Problem beim Programmieren von Vektorrechnern besteht meist darin, Programmteile, die vom Compiler
nicht vektorisiert werden können, durch Umstrukturieren des Programms doch zu vektorisieren. Das kann duch
eine einfache Umstellung einiger Anweisungen erfolgen, durch die Verwendung eines anderen Algorithmus
(wenn der ursprüngliche Algorithmus prinzipiell keine Vektorisierung zuläßt), es kann aber auch ganz
unmöglich sein, wenn das Problem entsprechend geartet ist.
__________________________________________________________________________________________
Seite 8 von 18
Markus Wegner, Michael Stange, I7I1
14.05.2016
Dependency Tests
Wir werden im folgenden einige Kriterien entwickeln, mit denen entschieden werden kann, ob in einer Schleife
die Gefahr einer Dependency vorliegt. Wir interessieren uns hierbei nur für Dependencies in Schleifen, weil nur
dort die Möglichkeit der Vektorisierung besteht.
Diese Kriterien werden wir anwenden, um in einigen, häufig auftretenden Situationen Dependencies aufzuspüren
und Methoden kennenzulernen, Dependencies zu umgehen.
Abb.6: Dependency-Analyse
Es gibt unterschiedliche Möglichkeiten für die Abhänigkeit zwischen verschiedenen Anweisungen, die sich wie
folgt klassifizieren lassen:
Wir betrachten hierfür ein Programmfragment, in dem die Anweisung A(I) = ... bezüglich einer Dependency
untersucht werden soll. Eine Dependency kann nur dann vorliegen, wenn A() in einem anderen Teil der Schleife
auftritt. Man unterscheidet, ob (siehe Abb.6)



weitere Bezug auf A() im Bereich vor (P) oder nach (S) der Anweisung A(I) =..... (wobei die rechte Seite
der Anweisung zum Bereich von “vorher“ gehört) liegt,
der Index des weiteren Bezugs größer (G) oder kleiner (L) als I ist,
der Index aufsteigend (I) oder absteigend (D) ist.
Es gibt 3 Kategorien mit jeweils 2 Möglichkeiten die Dependencies lassen sich also in 8 Gruppen aufteilen: PGI,
PLI, PGD, PLD, SGI, SLI, SLD, SGD. Nicht jeder dieser Gruppen hat eine Dependency zur Folge. Die Gefahr
einer Dependency besteht bei den Gruppen SLD, SGI, PLI und PGD.
__________________________________________________________________________________________
Seite 9 von 18
Markus Wegner, Michael Stange, I7I1
14.05.2016
Dependencies beheben
Umordnen von Anweisungen
Das folgende Programm besitzt eine Dependency vom Typ PLI
10
SUBROUTINE S(A, B, C, N)
DIMENSION A(100), B(100), C(100)
DO 10 I= 2, N
B(I)= A(I-1) * B(I) + C(I)
A(I)= C(I)
CONTINUE
Es darf nicht vektorisiert ablaufen, weil in diesem Fall zuerst die Anweisung
B(I)= A(I- 1) * B(I) + C(I)
für alle I ausgeführt würde und somit immer die alten Werte von A() benutzt würden. Bei skalarer Ausführung
wird durch
A(I)= C(I)
das in der nächsten Iteration verwendete A() verändert. Der Compiler erzeugt also einen sklararen Code. Wie
muß das Programm verändert werden, um einen vektorisierten Ablauf zu ermöglichen?
Es müssen lediglich die beiden Anweisungen vertauscht werden.
10
SUBROUTINE S(A, B, C, N)
DIMENSION A(100), B(100), C(100)
DO 10 I= 2, N
A(I)= C(I)
B(I)= A(I- 1) * B(I) + C(I)
CONTINUE
Diese Version des Programms ist vom Typ SLI; sie besitzt keine Dependency mehr. Natürlich muß man sich bei
dem Umordnen der Anweisungen davon überzeugen, daß das veränderte Programm gleiche Ergebnisse liefert.
Das so optimierte Programm benötigt bei N=100 nur 26% der Rechenzeit des ursprünglichen Programms.
Recurrence Threshold
Das folgende Programm enthält zwar eine Dependency, trotzdem kann es teilweise vektorisiert ablaufen:
10
SUBROUTINE S(A)
DIMENSION A(100)
DO 10 I= 6, 100
A(I)= A(I-5)+1
CONTINUE
END
__________________________________________________________________________________________
Seite 10 von 18
Markus Wegner, Michael Stange, I7I1
14.05.2016
A() wird zwar in der Schleife geändert, allerdings hat die Änderung erst nach 5 Iterationen einen Einfluß:
A(6) = A(1)  A(6) wird definiert
A(7) = A(2)
A(8) = A(3)
A(9) = A(4)
A(10)= A(5)
A(11)= A(6) nach 5 Iterationen wird A(6) benutzt
A(12)= A(7)
Jeweils 5 Iterationen können also auch vektorisiert ablaufen. Diese Tatsache wird als Recurrence Threshold von
5 bezeichnet. Der vom Compiler erzeugte Code wird die ersten 5 Elemente von A() in ein Vektorregister laden
und verarbeiten, dann die nächsten 5 u.s.w.. Ist die Recurrence Threshold größer als 63 (der Länge eines
Vektorregisters), kann der Code immer vektorisiert ablaufen.
Save Vector Length
Der Compiler kann sogar eine Variable Recurrence Threshold verarbeiten wie im folgenden Beispiel:
10
SUBROUTINE S(A, B, K, N)
DIMENSION A(-1000:1000), B(-1000:1000)
DO 10 I= K, N
A(I)= A(I-K)
CONTINUE
END
Hier ist die Recurrence Threshold zur Übersetzungszeit nicht bekannt. Der Compiler erzeugt einen Code, der zur
Laufzeit die maximal zulässige Vektorlänge ermittelt (save vector length).
Loop Unrolling
Das folgende Programm enthält eine Dependency, es kann also nicht vektorisiert werden.
10
SUBROUTINE S(A, B)
DIMENSION A(100), B(100)
DO 10 I= 2, 100
A(I) = A(I-1) * B(I)
CONTINUE
END
Beim Übersetzen dieses Programms wird die Schleife teilweise „aufgerollt“, d.h., einige Iterationen explizit
programmiert. Hierdurch wird eine hohe skalare Optimierung erreicht. Es ist sinnvoll, soviel Iterationen
aufzurollen, daß die Skalarregister möglichst alle genutzt werden.
10
SUBROUTINE S(A, B)
DIMENSION A(100), B(100)
DO 10 I= 2, 100-3,4
A(I)
= A(I-1) * B(I)
A(I+1) = A(I) * B(I+1)
A(I+2) = A(I+1) * B(I+2)
A(I+3) = A(I+2) * B(I+3)
CONTINUE
END
__________________________________________________________________________________________
Seite 11 von 18
Markus Wegner, Michael Stange, I7I1
14.05.2016
Partielle Vektorisierung
Viele umfangreiche Schleifen können wegen wenigen Anweisungen nicht vektorisiert werden. In diesem Fall
empfiehlt es sich, die Schleife in mehrere separate Schleifen aufzuteilen, von denen wenigstens einige
vektorisiert ablaufen können.
10
DO 10 I= 2, 10
A(I)= A(I-1)*5
B(I)=SIN(A(I))
CONTINUE
Diese Schleife ist wegen der ersten Anweisung nicht vektorisierbar, in der zweiten wird aber die meiste
Rechenzeit verbraucht, da die Sin-Berechnung aufwendig ist. Partielle Vektorisierung lohnt sich hier besonders,
da eine hochoptimierte Vektorversion der Sinusfunktion existiert, die durch folgende Programmänderung
genutzt werden kann:
10
DO 10 I= 2, 10
A(I)= A(I-1)*5 Skalare Ausführung
CONTINUE
20
DO 20 I= 2, 10
B(I)=SIN(A(I))Vektorisierte Ausführung
CONTINUE
Bedingte Vektorisierung
Das folgende Programm kann nicht vektorisiert werden, ohne den Wert IProblem zu kennen.
10
SUBROUTINE S(A, B, IProblem)
DIMENSION A(*), B(*)
DO 10 I= 1, 100
A(I+IProblem)= A(I)+B(I)
CONTINUE
END
Da zur Zeit der Übersetzung der Wert von IProblem unbekannt ist, kann die Schleife nicht unbedingt
vektorisiert werden. Sie ist vektorisierbar, wenn IProblem<1 oder IProblem>64 ist. Das Problem kann gelöst
werden, indem Code für skalare und vektorielle Ausführung erzeugt wird und per IF-Abfrage zur Laufzeit
entschieden wird, welcher Teil durchlaufen wird.
Loop Switching
Hier ein Standard-Beispiel für eine Dependency in der innersten Schleife:
10
SUBROUTINE S(A, B, N, X)
DIMENSION A(*), B(*)
DO 10 J= 1, N
DO 10 I= 1, N
A(I,J)=X*A(I-1,J)
CONTINUE
END
__________________________________________________________________________________________
Seite 12 von 18
Markus Wegner, Michael Stange, I7I1
14.05.2016
Hier sind beide Schleifen voneinander unabhäng, und die Dependency besteht nur in der Schleife über I. Deshalb
können die Schleifen einfach vertauscht werden (“Loop Switching“).
10
SUBROUTINE S(A, B, N, X)
DIMENSION A(*), B(*)
DO 10 I= 1, N
DO 10 J= 1, N
A(I,J)=X*A(I-1,J)
CONTINUE
END
Matrix Multiplikation
Folgendes Programm führt eine Matrix-Multiplikation (A() = B() x C()) durch. Die Verschachtelung der 3
Schleifen ist so ausgeführt, daß die Anweisungen in dieser Reihenfolge auch ein Mensch ausführen würde:
DO 10 I = 1, N
DO 10 J = 1, N
A(I,J) = 0.
DO 10 K = 1, N
A(I,J)= A(I,J) + B(I,K) * C(K,J)
10
CONTINUE
Bei der Ausführung auf einem Vektorrechner kann zwar die Multiplikation B(I,K) * C(K,J) vektorisiert
durchgeführt werden, aber A(I,J) ist in der innersten Schleife ein Skalar; die Schleife hat also die Form:
Skalar = Skalar + Vektor * Vektor, die nicht vektorisiert werden kann. Durch folgende Umordnung der Schleifen
erhält die innnerste Schleife die Form: Vektor = Vektor + Vektor * Skalar, die sehr gut vektorisiert werden kann:
10
20
DO 10 I = 1, N
DO 10 J = 1, N
A(J,I) = 0.
CONTINUE
DO 20 K = 1, N
DO 20 J = 1, N
DO 20 I = 1, N
A(I,J)= A(I,J) + B(I,K) * C(K,J)
CONTINUE
Durch diese Form wird die Geschwindigkeit fast verdoppelt, obwohl jedes Element von A jetzt N-mal häufiger
aus dem Speicher gelesen und geschrieben werden muß. Dies ist auf der CRAY kein Problem, weil sie 3 Pfade
zum Memory besitzt, die dann voll genutzt werden.
Weitere Verfahren sind:
 Indirekte Adressierung
 Dependencies in Mehrdimensionalen Felden
 Reduction Loops
__________________________________________________________________________________________
Seite 13 von 18
Markus Wegner, Michael Stange, I7I1
14.05.2016
Performance Gewinn durch Vektorisierung (Amdahls Gesetz)
Jedes Programm besteht aus einem vektorisierbaren und einem skalar ablaufendem Anteil. Amdahls Gesetz ist
ein Maß für den Performancegewinn durch Vektorisierung (oder auch Parallelisierung) in Abhängigkeit des
vektorisierbaren Anteils an dem Programm. Hierzu betrachten wir folgende Größen:



R = Geschwindigkeitsgewinn durch Vektorisierung im Vergleich zur skalaren Ausführung (bei der CRAY
typischerweise 10-20)
V = Anteil der Operationen, die vektorisiert ablaufen
S = 1-V = Anteil der Operationen, die skalar ablaufen müssen
Der Speedup A durch Vektorisierung ist dann
A = skalare Ausführungszeit / gemischte Ausführungszeit = T S / TG
In der Zeit TG wird der vektorisierte Anteil des Programmms auch vektorisiert verarbeitet (mit R-facher
Geschwindigkeit), der Rest wird skalar verarbeitet, d.h.:
TG = S * TS + V * TS / R
Dieser Zusammenhang wir Amdahls Gesetz genannt. Er zeigt, daß der skalare Anteil eines Programms
dominierend für die Performance ist. In der Realität kann R über 10 liegen, häufig ist R aber kleiner 10 wegen
zusätzlichen Overhead oder zu kurzen Vektorlängen. Ein spürbarer Performancegewinn kann nur dann erzielt
werden, wenn fast das gesamte Programm vektorisiert ist. Ist dies nicht der Fall, ist die Geschwindigkeit des
Skalarteils dominierend für die Performance.
Ein Vektorrechner ist nur dann schnell, wenn er auch einen schnellen Skalarteil besitzt, der die
Grundperformance angibt.
Es ist zu beachten, daß in vielen Programmen der größte Teil der CPU-Zeit in einem kleinen Teil des Programms
verbraucht wird (häufig werden in 20% des Programms 80% der CPU-Zeit verbraucht). Deshalb bringt die
Vektorisierung in einem kleinen Teil des Programms oft einen spürbaren Performancegewinn, weil in Amdahls
Gesetz der zeitliche Anteil des Programms zählt.
__________________________________________________________________________________________
Seite 14 von 18
Markus Wegner, Michael Stange, I7I1
14.05.2016
Speichersysteme
Realer Speicher
Bei der CRAY und anderen Supercomputern wird aus Performancegründen kein virtueller Speicher verwendet.
Das führt dazu, daß jeder Prozeß an einer bestimmten realen Speicherstelle beginnt, und sein gesamter benutzter
Speicher in den folgenden Speicherstellen liegt. Möchte der Prozeß seine Größe ändern, kann er dies nur bis zum
Beginn des nächsten Jobs. Wird ein Prozeß beendet, hinterläßt er eine freie Lücke, die von anderen Prozessen
genutzt werden kann. Da jeder Prozeß einen zusammenhängenden Speicherbereich benötigt, kann es sein, daß
zwar insgesamt genug Speicherplatz vorhanden ist, aber die größte freie Lücke gerade zu klein für einen neuen
Prozeß ist. Das Memory ist zu stark fragmentiert. In diesem Fall ordnet das BS die Prozesse um (SHUFFLE) und
erzeugt damit eine große Lücke.
Ein weiterer Nachteil vieler Realisierungen von realem Speicher liegt darin, daß ein Prozeß immer in seiner
Gesamtheit im Speicher oder außerhalb des Speichers (SWAP) ist, während bei einem Rechner mit virtuellem
Speicher auch Teile eines Programms ausgelagert weren können, da hier mit Seiten oder Segmenten gearbeitet
wird.
Bei folgender Konstellation, die leider in der Praxis häufiger auftritt, wird dieser Nachteil besonders deutlich:
Nur 2 Prozesse sind aktiv, von denen jeder 55% des verfügbaren Speichers benötigt. Es kann also nur jeweils
einer der beiden Prozesse im Speicher sein. Wenn dieser die CPU nicht nutzen kann (weil er z.B. auf ein
Peripherie-Gerät wartet), bleibt die CPU ungenutzt. Das Auslagern des aktiven Jobs und ins Memory Holen des
wartenden Jobs dauert sehr lange (wenn z.B. jeder Job 30MB groß ist, wären das mindestens 6s). In dieser Zeit
ist keiner der beiden Prozesse aktiv. Wenn bei einer solchen Konstellation beide Prozesse viele I/O-Operationen
durchführen, kann es sein, daß die CPU die meiste Zeit ungenutzt ist. Hier kann man nur auf einen weiteren
Prozeß hoffen, der weniger als 40% des Speichers benötigt und wenige I/O-Operationen durchführt. Dieser
könnte dann die freie CPU-Zeit nutzen.
Der große Vorteil des realen Speichers ist seine hohe Geschwindigkeit. Während bei einem virtuellen System für
einen Speicherzugriff zunächst aus der virtuellen Adresse die reale Adresse berechnet und für den Zugriff
möglicherweise eine zusätzliche I/O-Operation durchgeführt werden muß, kann bei einem System mit realem
Speicher der Zugriff immer direkt erfolgen.
__________________________________________________________________________________________
Seite 15 von 18
Markus Wegner, Michael Stange, I7I1
14.05.2016
Abb.7: Memory-Bandbreite einer CRAY-YMP für verschiedene Strides. Durch das reale Speicherkonzept
besitzt diese Kurve keinen Trend nach oben wie bei der Convex (Abb.8)
Abb.8: Memory-Bandbreite einer Convex C3800 für verschiedene Strides
__________________________________________________________________________________________
Seite 16 von 18
Markus Wegner, Michael Stange, I7I1
14.05.2016
Memory Banks
Jedes Register ist in der Lage, in jedem Taktzyklus ein Wort zu lesen bzw. zu schreiben. Da die Kosten für
solche Register sehr hoch sind, ist es nicht praktikabel, mehr als einige hundert von ihnen in einem Rechner zu
haben. Der Hauptspeicher besteht aus Millionen von Speicherzellen, die billiger und damit auch langsamer sind.
Bei der CRAY-YMP ist das Memory für einen Zugriff 8 Zyklen blockiert. Durch das Konzept der MemoryBanks ist es trotzdem möglich, ein Speichersystem zu besitzen, das in jedem Taktzyklus ein Speicherzugriff
ermöglicht.
Der gesamt Speicher ist in mehrere (CRAY-YMP: je nach Anzahl der CPU´s 64 oder mehr) Bänke unterteilt, die
in gewisser Weise voneinander unabhängig arbeiten können: Wenn eine Bank durch einen Zugriff blockiert ist,
können die anderen weitere Zugriffe verarbeiten. Der Speicher ist so organisiert, daß aufeinanderfolgende
Speicherstellen in unterschiedlichen Bänken liegen.
__________________________________________________________________________________________
Seite 17 von 18
Markus Wegner, Michael Stange, I7I1
14.05.2016
Quellen




Klaus Schmidt „Programmierung von Vektorrechnern und Parallelrechnern“,
Verlag Harry Deutsch, Frankfurt am Main, Thun, 1994, ISBN: 3-8171-1360-9
Wilhelm Gehrke „Fortran 90 Referenz-Handbuch“,
Carl Hanser Verlag München, Wien, 1991, ISBN: 3-446-16321-2
Brigitte und Rainer Wojciescynski „Fortran 90 Programmieren mit dem neuen Standard“
Addison Wesley, Bonn, 1993, ISBN: 3-89319-600-5
Rudolf Herschel „Fortran Systematische Darstellung für den Anwender“
R.Oldenburg Verlag, München, 1978, ISBN: 3-486-22431-X
__________________________________________________________________________________________
Seite 18 von 18
Herunterladen