Paralleles Rechnen Konzepte und Anwendungen im Data Mining

Transcription

Paralleles Rechnen Konzepte und Anwendungen im Data Mining
Paralleles Rechnen
Konzepte und Anwendungen im Data
Mining
Stefan Wissuwa
1. Dezember 2008
Thesis zur Erreichung des Grades
Master of Science (M.Sc.) in Wirtschaftsinformatik
Hochschule Wismar - Fakultät für Wirtschaftswissenschaften
Eingereicht von: Stefan Wissuwa, Dipl. Wirt.-Inf. (FH)
Erstbetreuer: Jürgen Cleve, Prof. Dr. rer. nat.
Zweitbetreuer: Uwe Lämmel, Prof. Dr.-Ing.
Inhaltsverzeichnis
1 Einleitung
1.1 Motivation . . . . . .
1.2 Anwendungsgebiete .
1.3 Inhalt der Arbeit . . .
1.4 Begriffsklärung . . .
I
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
Grundlagen der parallelen Datenverarbeitung
5
2 Stufen der Parallelisierung
6
3 Parallele Architekturen
3.1 Flynnsche Klassifizierung . . . . . . . . . .
3.2 Speichermodelle . . . . . . . . . . . . . .
3.2.1 Rechner mit gemeinsamem Speicher
3.2.2 Rechner mit verteiltem Speicher . .
3.3 Prozesse / Threads . . . . . . . . . . . . .
3.4 Client-Server-Architekturen . . . . . . . .
3.5 Cluster-Computing . . . . . . . . . . . . .
3.6 Grid-Computing . . . . . . . . . . . . . . .
4 Parallelisierungsebenen
4.1 Parallelität auf Instruktionsebene
4.2 Parallelität auf Datenebene . . .
4.3 Parallelität in Schleifen . . . . .
4.4 Parallelität auf Funktionsebene .
1
1
2
3
4
.
.
.
.
.
.
.
.
.
.
.
.
5 Parallele Programmiermodelle
5.1 Darstellung . . . . . . . . . . . . . .
5.2 Strukturierung . . . . . . . . . . . . .
5.3 Datenverteilung und Kommunikation .
5.3.1 Broadcast . . . . . . . . . . .
5.3.2 Scatter . . . . . . . . . . . .
5.3.3 Gather . . . . . . . . . . . . .
5.3.4 Reduktion . . . . . . . . . . .
5.4 Synchronisation . . . . . . . . . . . .
5.4.1 Kritische Abschnitte . . . . .
5.4.2 Barrieren . . . . . . . . . . .
5.4.3 Locks . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
7
7
7
8
9
10
10
11
11
.
.
.
.
13
13
13
14
14
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
16
16
17
17
17
18
18
19
19
20
20
20
6 Einflussfaktoren Paralleler Programme
6.1 Parallele Skalierbarkeit . . . . . . . . .
6.1.1 Speedup . . . . . . . . . . . . .
6.1.2 Amdahl’sches Gesetz . . . . . .
6.1.3 Gustafson-Gesetz . . . . . . . .
6.1.4 Karp-Flatt-Metrik . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
21
21
21
21
22
24
6.2
6.3
6.4
II
Load Balancing und Scheduling . . . . . . . . . . . . . . . . . . . . . . . . .
Lokalität . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Speichersynchronisation . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Bibliotheken und Systeme
7 Unified Parallel C
7.1 Verfügbarkeit . . . . . .
7.2 Parallelisierungskonzept
7.3 Speichermodell . . . . .
7.4 Synchonisation . . . . .
7.5 Globale Operatoren . . .
25
25
26
28
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
29
29
29
30
32
32
8 Message Passing Interface
8.1 Verfügbarkeit . . . . . . .
8.2 Parallelisierungskonzept .
8.3 Speichermodell . . . . . .
8.4 Synchronisation . . . . . .
8.5 Globale Operatoren . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
33
34
34
34
34
34
.
.
.
.
.
35
35
35
35
36
37
.
.
.
.
38
38
38
38
38
.
.
.
.
.
.
.
.
.
.
40
40
40
40
42
42
43
45
45
46
48
9 OpenMP
9.1 Verfügbarkeit . . . . . .
9.2 Parallelisierungskonzept
9.3 Speichermodell . . . . .
9.4 Synchronisation . . . . .
9.5 Globale Operatoren . . .
10 Andere
10.1 BOINC . . . . . . . . .
10.2 BLAS . . . . . . . . . .
10.3 PThreads . . . . . . . .
10.4 Parallel Virtual Machine
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
11 Vergleichende Betrachtung
11.1 Implementierung . . . . . . . . . . . .
11.1.1 Matrizenmultiplikation Seriell .
11.1.2 Matrizenmultiplikation UPC . .
11.1.3 Matrizenmultiplikation MPI . .
11.1.4 Matrizenmultiplikation OpenMP
11.2 Performance . . . . . . . . . . . . . . .
11.3 Bewertung . . . . . . . . . . . . . . . .
11.3.1 UPC . . . . . . . . . . . . . . .
11.3.2 MPI . . . . . . . . . . . . . . .
11.3.3 OpenMP . . . . . . . . . . . .
III Parallelisierung im Data Mining
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
49
12 Data Mining Grundlagen
12.1 Ablaufmodell . . . . . . . . . . . .
12.2 Klassifikation der Verfahren . . . .
12.3 Künstliche Neuronale Netze . . . .
12.3.1 Feed-Forward-Netze . . . .
12.3.2 Selbstorganisierende Karten
.
.
.
.
.
50
50
51
53
53
54
13 Parallele Algorithmen
13.1 Serielle Selbstorganisierende Karte . . . . . . . . . . . . . . . . . . . . . . . .
13.2 Parallele Selbstorganisierende Karte . . . . . . . . . . . . . . . . . . . . . . .
13.3 Performance Test . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
56
56
57
59
14 Parallele Modelle
14.1 Hierarchische Kohonen-Karten . . . . . . . .
14.1.1 Konzept . . . . . . . . . . . . . . . .
14.1.2 Umsetzung . . . . . . . . . . . . . .
14.1.3 Testaufbau . . . . . . . . . . . . . .
14.1.4 Vergleich der Modellqualität . . . . .
14.1.5 Vergleich der Rechengeschwindigkeit
14.2 Feed-Forward-Netze . . . . . . . . . . . . .
14.2.1 Konzept . . . . . . . . . . . . . . . .
14.2.2 Umsetzung . . . . . . . . . . . . . .
14.2.3 Vergleich der Modellqualität . . . . .
14.2.4 Vergleich der Rechengeschwindigkeit
61
61
61
64
67
70
72
73
73
73
74
75
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
IV Zusammenfassung und Ausblick
76
V
79
Ehrenwörtliche Erklärung
VI Anhang
80
A Schnittstellen und Bibliotheken
A.1 UPC Konsortium . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
A.2 Compilerübersicht . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
81
81
82
B Messwerte
B.1 Messwerte Matrizenmultiplikation
B.2 Messwerte SOM . . . . . . . . .
B.3 Messwerte Feed-Forward-Netz . .
B.4 Inhalt der CD . . . . . . . . . . .
84
84
86
88
89
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
Algorithmenverzeichnis
1
2
3
4
5
Matrizenmultiplikation Seriell . . .
Matrizenmultiplikation MPI Global
SOM Seriell . . . . . . . . . . . . .
Parallele SOM mit OpenMP . . . .
Hierarchische SOM . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
41
43
57
59
68
Abbildungsverzeichnis
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
Gemeinsames Speichermodell UMA . . . . . . . . . . . . .
Gemeinsames Speichermodell NUMA . . . . . . . . . . . .
Verbindungsnetzwerke . . . . . . . . . . . . . . . . . . . .
Effiziente Broadcast-Operation . . . . . . . . . . . . . . . .
Effiziente Akkumulations-Operation . . . . . . . . . . . . .
Parallele Skalierbarkeit nach Amdahl . . . . . . . . . . . . .
Parallele Skalierbarkeit nach Amdahl . . . . . . . . . . . . .
Speedup Amdahl vs. Gustafson . . . . . . . . . . . . . . . .
Row- vs. Column-first Ordering . . . . . . . . . . . . . . .
Message-Passing . . . . . . . . . . . . . . . . . . . . . . .
OpenMP Fork-Join-Modell . . . . . . . . . . . . . . . . . .
Performance UPC . . . . . . . . . . . . . . . . . . . . . . .
Performance OpenMP . . . . . . . . . . . . . . . . . . . .
Performance MPI . . . . . . . . . . . . . . . . . . . . . . .
Performance MPI vs. MPI Global . . . . . . . . . . . . . .
Performance OpenMP vs. MPI Global . . . . . . . . . . . .
Performance OpenMP vs. MPI Global . . . . . . . . . . . .
CRISP-DM Phasenmodell . . . . . . . . . . . . . . . . . .
Rechenzeit SOM OpenMP für kleine Karten . . . . . . . . .
SOM OpenMP Rechenzeiten und Speedup nach Kartengröße
U-Matrix einer Kohonen-Karte . . . . . . . . . . . . . . . .
Aufteilung einer SOM . . . . . . . . . . . . . . . . . . . .
Interpolation der Gewichte . . . . . . . . . . . . . . . . . .
Clusterqualität vs. Lernrate / Kartengröße . . . . . . . . . .
U-Matrix Seriell . . . . . . . . . . . . . . . . . . . . . . . .
U-Matrix Hierarchisch . . . . . . . . . . . . . . . . . . . .
Clusterqualität . . . . . . . . . . . . . . . . . . . . . . . . .
Speedup SOM Parallel / Seriell . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
8
8
9
18
19
22
23
24
26
33
36
44
45
45
46
47
47
51
60
60
62
63
63
69
71
71
71
72
Tabellenverzeichnis
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Linpack Top-5 Supercomputer Stand 06/2008 . . . . . . . . . . . . . .
Übersicht UPC-Compiler . . . . . . . . . . . . . . . . . . . . . . . . .
OpenMP-fähige Compiler . . . . . . . . . . . . . . . . . . . . . . . .
Matrizenmultiplikation Seriell . . . . . . . . . . . . . . . . . . . . . .
Matrizenmultiplikation UPC . . . . . . . . . . . . . . . . . . . . . . .
Matrizenmultiplikation OpenMP . . . . . . . . . . . . . . . . . . . . .
Matrizenmultiplikation MPI Master-Worker . . . . . . . . . . . . . . .
Matrizenmultiplikation MPI Global . . . . . . . . . . . . . . . . . . .
Clusterqualität (seriell) in Abhängigkeit von Lernrate und Kartengröße .
Clusterqualität (parallel) in Abhängigkeit von Lernrate und Kartengröße
Rechenzeit in Abhängigkeit von Kartengröße und Parallelisierungsgrad
Berechnungszeiten Parallele SOM mit OpenMP . . . . . . . . . . . . .
Erkennungsrate Teilklassifikatoren . . . . . . . . . . . . . . . . . . . .
Rechenzeiten und Erkennungsrate n-Fach vs. binär nach Netzgröße . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
12
82
83
84
84
85
85
86
86
87
87
88
88
89
1 Einleitung
1.1 Motivation
Parallelrechner, Mehrkernprozessoren, Grid-Computing und seit geraumer Zeit Cloud-Computing sind Schlagworte, die immer dann genannt werden, wenn es um enorme Rechenleistung
von Computern geht. Doch um was handelt es sich dabei genau?
Die Entwicklung schnellerer Prozessoren erfolgte bisher zu einem signifikanten Anteil durch
immer stärkere Miniaturisierung der Schaltkreise, was eine höhere Zahl an Transistoren pro
Chip und höhere Taktraten erlaubte. Die Anzahl der Transistoren verdoppelt sich etwa alle 18
Monate. Dieser Zusammenhang wurde 1965 empirisch durch Gordon Moore festgestellt und
wird daher auch als Moore’sches Gesetz bezeichnet1 . Obwohl der dafür notwendige technologische Aufwand mit der Zeit immer größer wurde, wird diese Steigerungsrate auch in nächster Zukunft beibehalten werden können, bis die physikalische Grenze erreicht ist. Eine stetige
Steigerung der Taktraten stellt sich bereits als sehr viel schwieriger heraus, da die damit verbundene Abwärme des Prozessors nur sehr schwer zu handhaben ist und bereits Werte erreicht
hat, die - relativ zur Oberfläche - etwa der Heizleistung einer Herdplatte gleichkommen. Statt
die Geschwindigkeit nur durch höhere Taktraten zu steigern, wurde die Komplexität der Prozessoren so weit erhöht, dass bereits mehrere Prozessorkerne auf einem Chip zusammengefasst
werden und somit das, was man unter einem Prozessor-Chip versteht, selbst einen kleinen Parallelrechner bildet.2 Doch auch eine stetige Erhöhung der Anzahl der Prozessorkerne ist nicht
unbedingt sinnvoll. Da Prozessoren heute um ein Vielfaches schneller Arbeiten als Daten vom
Hauptspeicher zum Prozessor übertragen werden können, sind schnelle, teure und damit kleine
Zwischenspeicher - Caches - notwendig. Da jeder Prozessorkern einen eigenen Cache besitzt,
aber für alle Prozessorkerne ein konsistenter Speicherinhalt sichergestellt sein muss, sind Synchronisationsmechanismen notwendig, deren Komplexität mit der Zahl der Prozessoren steigt.
Da eine Synchronisation zudem Zeit kosten, schmälert dies den Geschwindigkeitsgewinn durch
zusätzliche Prozessorkerne. In bestimmten Fällen können zusätzliche Prozessoren sogar dazu
führen, dass ein Programm langsamer wird3 . Diese vielfältigen Abhängigkeiten, die so auch in
größerem Maßstab für Rechennetze gelten, führen dazu, dass Parallelisierung auf technischer
wie auch auf softwaretechnischer Seite keine triviale Aufgabe ist.
Parallelrechner waren bis vor wenigen Jahren hauptsächlich in der wissenschaftlichen Simulation im Einsatz. Sie erlaubten es erstmals, komplexe Phänomene, deren physische Analyse zu
aufwändig, teuer oder gefährlich ist, am Computer zu simulieren. Rund um diese Simulationstechnik hat sich ein eigener Wissenschaftszweig etabliert, der unter dem Begriff Computational
Science bekannt ist.
Beginnend mit der Einführung der ersten Pentium Dual-Core Prozessoren für den ConsumerMarkt durch die Firma Intel Corp., sind heute kaum noch aktuelle PCs auf dem Markt zu finden,
die nicht mindestens einen Doppelkern-Prozessor enthalten. Obwohl die jeweiligen Ziele, für
die diese Technologien entwickelt werden, höchst unterschiedlich sind, gleichen sie sich jedoch
in einem Punkt: die Steigerung der Leistungsfähigkeit von Computersystemen erfolgt nicht
hauptsächlich durch höhere Taktrate, sondern durch Parallelisierung. Dies wird dazu führen,
1
Vgl.: [R AUBER 2007] S.100
Vgl.: [R AUBER 2008] S.6ff.
3
Vgl.: Linux-Magazin, Ausgabe 11/2008, MySQL
2
-1-
dass verstärkt parallele Programmiertechniken in der Softwareentwicklung eingesetzt werden
müssen, um die Leistung auch nutzen zu können.
Doch auch durch Parallelisierung können nicht unbegrenzt nutzbar höhere Rechengeschwindigkeiten erzielt werden. Die Leistungsangaben für Supercomputer mögen einen in Erstaunen
versetzen, jedoch ist dabei zu beachten, dass diese Leistung nur für sehr spezielle Programme auch wirklich genutzt werden kann - nämlich für Programme oder Algorithmen, die sich
überhaupt parallelisieren lassen. Die 1,026 Peta-Flop/s des derzeit weltweit schnellsten Supercomputers4 werden nur durch Kombination von über hunderttausend Mehrkern-Prozessoren
erreicht. Diese Leistung zur Lösung einer Aufgabe bündeln zu wollen bedeutet, die Aufgabe in
über hunderttausend separate Teilaufgaben zu zerlegen, was weder immer möglich noch stets
sinnvoll ist5 .
Eine Bürosoftware wird auf einem Parallelrechner nicht unbedingt schneller ausgeführt werden, da sie meist nur einen einzigen der vorhandenen Prozessoren nutzen kann. Eine komplexe
physikalische Simulation hingegen kann enorm von der Verteilung auf mehrere Prozessoren
profitieren, sofern der Algorithmus diese Aufteilung zulässt. Data-Mining ist ein weiteres Anwendungsgebiet, für das Parallelisierung vorteilhaft sein kann, da sehr große Datenmengen zu
verarbeiten sind und dafür komplexe Algorithmen eingesetzt werden.
Die langen Rechenzeiten, die für das Erstellen eines Data-Mining-Modells notwendig sind,
behindern eine interaktive und intuitive Arbeitsweise bei der Exploration von Datenmengen.
Um Zeit zu sparen, können mehrere unterschiedliche Modelle nach dem Versuch-und-IrrtumPrinzip parallel berechnet werden. Dies erfordert jedoch große Planungssorgfalt und führt in
der Regel zu vielen überflüssigen Berechnungen und damit zur Vergeudung von Rechenzeit.
Ein Ansatz ist, die Gesamtrechenzeit eines Experiments zu verringern, in dem sowohl der DataMining-Prozess als auch einzelne Algorithmen parallel ausgeführt werden. Vor dem Hintergrund, dass selbst aktuelle, kostengünstige PCs häufig Mehrkern-Prozessoren enthalten, ist eine
Parallelisierung umso interessanter.
1.2 Anwendungsgebiete
Für parallele Rechnerarchitekturen lassen sich prinzipiell zwei Anwendungsgebiete unterscheiden:6
Das High-Availability-Computing dient der Zurverfügungstellung ausfallsicherer Dienste, beispielsweise für Datenbanken oder Web-Server, indem alle anfallenden Aufgaben nach Bedarf
auf einzelne Knoten verteilt werden und dadurch auch der Ausfall einzelner Knoten kompensiert werden kann.
Das High-Performance-Computing dient vor allem der Bündelung von Rechenleistung, um eine einzelne Aufgabe entweder schneller oder sehr viel genauer zu lösen7 . Dies ist auch das
Anwendungsgebiet paralleler Algorithmen und Inhalt dieser Arbeit.
Es existieren eine Reihe von wissenschaftlichen Anwendungen, die in großem Maßstab auf
Parallelisierung setzen und die durch die Art, diese zu realisieren, in der Öffentlichkeit große
4
Siehe Tabelle 1 auf Seite 12.
Siehe Kapitel 6 auf Seite 21.
6
Vgl.:[BAUKE 2006] S. 31ff.
7
Zur Unterscheidung siehe Kapitel 6.
5
-2-
Popularität erlangt haben. Die wohl bekanntesten Vertreter gehören zur Gruppe der auf dem
BOINC-Framework8 basierenden at-Home-Projekte, bei denen durch ans Internet angeschlossenen PCs ein Parallelrechner nach dem Master-Worker-Prinzip aufgebaut wird. Da hierbei
auch sehr viele Privat- und Bürorechner zum Einsatz kommen, werden diese Verfahren auch als
Desktop-Grid-Computing bezeichnet. Die bekanntesten Projekte sind Seti@Home9 und Folding@Home10 .
Seti@Home dient der Analyse von Radiosignalen auf Muster, die von einer extraterrestrischen
Intelligenz stammen könnten. Obwohl bisher erfolglos, ist das Projekt wohl unbestritten der
populärste Vertreter seiner Art, vor allem unter Privatpersonen. Diese stellen Rechenzeit zur
Verfügung, die sie selbst nicht nutzen, indem sie eine spezielle Software - in der ursprünglichen
Version ein Windows-Bildschirmschoner - installieren, der Datenpakete von einem Server lädt
und das Ergebnis der Berechnung zurückschickt.
Folding@Home funktioniert auf die gleiche Weise, nur ist das Ziel die Bestimmung der Tertiärstruktur11 von Proteinen. Da die Funktion eines Proteins nicht nur von dessen chemischer
Zusammensetzung - die durch die DNA codierte Aminsosäuresequenz - abhängt, sondern auch
von dessen räumlicher Struktur. Die Ausbildung der dreidimensionalen Struktur aus einer langen Kette von Aminosäuren wird als Faltung bezeichnet, wobei es mehrere Varianten gibt. Nur
die physiologisch korrekte Form kann die ihr zugedachte Funktion erfüllen, falsch gefaltete
Proteine haben eine reduzierte, keine oder im schlimmsten Fall pathogene Funktion. Es wird
vermutet, dass Krankheiten wie BSE oder die Kreutzfeldt-Jakob-Krankheit durch falsch gefaltete Proteine (Prionen) hervorgerufen werden. Neben der Erforschung, wie und unter welchen
Bedingungen der Faltungsprozess genau funktioniert, ist die Bestimmung der Tertiärstruktur ein
wichtiger Schritt, wenn es gilt, die Funktion eines neu entdeckten Gens des dadurch kodierten
Proteins herauszufinden.
Weitere, jedoch weniger populäre Projekte sind zum Beispiel Docking@Home12 zur Untersuchung der Molekülbindungen zwischen Liganden und Proteinen in der Biochemie sowie
NQueens@Home13 zur Lösung des N-Damen-Problems auf Feldern größer als 26x26 Einheiten.
1.3 Inhalt der Arbeit
Diese Arbeit befasst sich mit den Anwendung von Parallelisierungstechniken auf dem Gebiet
des Data-Mining. Da das Thema dieser Arbeit viele Teilbereiche der Informatik berührt, die
selbst wiederum sehr Umfangreich sind, gliedert sich diese Arbeit in drei Teile, die jedes für
sich eine eigenständige thematische Einheit bilden.
Der Erste Teil stellt eine hauptsächlich konzeptionelle Einführung in die Thematik des parallelen Rechnens dar. Es werden Begriffe und die theoretischen Grundlagen erläutert sowie
grundlegende Modelle und Konzepte vorgestellt.
Der Zweite Teil widmet sich konkreten Implementationen der im ersten Teil vorgestellten Konzepte. Der Schwerpunkt liegt auf Sprachen, Schnittstellen und Bibliotheken für die parallele
8
Siehe dazu Kapitel 10.1.
http://setiathome.berkeley.edu/
10
http://folding.stanford.edu/
11
Vgl.: [K NIPPERS 2001] S. 37ff.
12
http://docking.cis.udel.edu/
13
http://nqueens.ing.udec.cl/
9
-3-
Programmierung, die relativ verbreitet und frei verfügbar sind. Es werden ausgewählte Schnittstellen vorgestellt, deren Arbeitsweise anhand von Beispielen erläutert und eine erste vergleichende Bewertung vorgenommen.
Der Dritte Teil behandelt das Thema Data Mining und ausgewählte Algorithmen. Es wird untersucht, inwiefern sich Verfahren parallelisieren lassen, welcher Aufwand hierfür notwendig
ist und welche Resultate erzielt werden können. Insbesondere die Parallelisierung von DataMining-Modellen unter Verwendung vorhandener, rein sequentieller Data-Mining-Algorithmen
wird untersucht.
1.4 Begriffsklärung
Da aufgrund der Komplexität des Themas und der Vielzahl der angeschnittenen Themen zwangsläufig Überschneidungen von Begriffen auftreten, werden der Einfachheit halber folgende Begriffe verwendet:
Als „Prozessor” wird in dieser Arbeit nicht der physikalische Chip, sondern die tatsächlich
ausführende Recheneinheit bezeichnet. Demzufolge besitzt ein Computer mit einem MehrkernProzessor-Chip eine entsprechende Anzahl an Prozessoren. Diese Sichtweise ist aus dem Grund
günstig, da es sich hier hauptsächlich um die Software-Sicht der parallelen Programmierung
geht, und weniger um die Hardware. Zudem stellt sich ein Mehrkern-Prozessor aus Sicht eines
Programms ebenfalls als eine Anzahl von Einzelprozessoren dar14 .
In der Literatur wird zwischen Prozessen und Threads unterschieden, in dieser Arbeit wird nur
der Begriff Prozesse verwendet. Der Grund dafür ist, dass sich Prozesse und Threads hauptsächlich durch die Art der Ressourcenteilung der Kindprozesse mit dem Elternprozess unterscheiden. Da die hier vorgestellten Programme und Bibliotheken unter Linux eingesetzt werden und
Linux nur Prozesse unterstützt, ist die Nutzung von Threads zwangsläufig mit einer Emulation
durch Prozesse verbunden.
14
Vgl.:[R AUBER 2007] S.101
-4-
Teil I
Grundlagen der parallelen
Datenverarbeitung
In diesem Kapitel werden die theoretischen Grundlagen verschiedener Ansätze zur Parallelisierung von Algorithmen vorgestellt. Dabei werden konkrete Methoden, Konzepte und Umsetzungsmöglichkeiten gezeigt.
This chapter provides an introduction to the theory of parallel programming, concepts and
techniques. It shows the most popular approaches, methods and concepts for parallelization of
algorithms.
-5-
2 Stufen der Parallelisierung
Unter Parallelisierung wird die Zerlegung eines Problems in Teilprobleme verstanden, die gleichzeitig von mehreren Prozessoren verarbeitet werden können, so dass die Berechnung weniger
Zeit benötigt, als würde sie auf einem einzelnen Prozessor erfolgen. Auf diese Weise lässt sich
ein gegebenes Problem in kürzerer Zeit oder aber ein größeres Problem in der selben Zeit lösen.
Es lassen sich vier Stufen der Parallelisierung unterschieden15 , wobei sich die ersten drei Stufen Wortbreite, Pipelining und Superskalare Prozessoren direkt auf die verwendete Technik im
Prozessorkern beziehen. Die letzte Stufe, Parallelisierung auf Prozessebene, ist eher ein programmtechnisches Konstrukt, das nicht mehr direkt vom Prozessor abhängt16 , sondern vom
Betriebssystem übernommen wird.
Die Wortbreite ist der Anzahl der Bits, die in einer Operation vom Prozessor gleichzeitig verarbeitet werden können und entspricht somit der internen Busbreite und der Registergrösse.
Die Wortbreite bestimmt maßgeblich, wie viele Bytes bei einem Speicherzugriff gleichzeitig
übertragen werden können. Aktuelle Prozessoren haben eine Wortbreite von 32 oder 64 Bit.
Eine Instruktion besteht aus mehreren atomaren Operationen, die in der Regel mindestens die
folgenden Schritte umfasst:
1. fetch: Laden der nächsten Instruktion aus dem Speicher,
2. decode: Dekodieren der Instruktion,
3. execute: Bestimmung von Quell- und Zieladressen der Operanden und Ausführen der Instruktion,
4. write back: Zurückschreiben des Ergebnisses.
Unter Pipelining versteht man die Ausführung solcher atomaren Operationen durch separate Hardwareeinheiten, wodurch eine zeitlich überlappende Ausführung mehrerer Instruktionen
möglich wird. Die Verarbeitung der nachfolgenden Instruktion kann im günstigsten Fall bereits
mit der fetch-Operation beginnen, sobald die vorhergehende Instruktion die decode-Operation
durchläuft. Dies funktioniert jedoch nur dann, wenn keine Abhängigkeiten zwischen den Instruktionen bestehen17 .
Superskalare Prozessoren18 erweitern das im Pipelining eingesetzte Konzept separater Hardwareeinheiten für atomare Operationen auf Instruktionsebene. Dazu verfügt der Prozessor über separate Funktionseinheiten für bestimmte Aufgaben, zum Beispiel für Ganzzahlarithmetik (ALU
- arithmetic logical unit), Fließkommaarithmetik (FPU - floatin point unit) und Speicherzugriffe
(MMU - memory management unit). Die Beschränkung der parallelen Ausführung hinsichtlich
der Abhängigkeiten nachfolgender Instruktionen gelten auch hier.
Moderne Prozessoren enthalten darüber hinaus auch spezielle erweiterte Befehlssätze wie MMX
(Multi-Media-Extension) oder SSE (Streaming SIMD Extension)19 , die für die parallele Verar15
Vgl.: [R AUBER 2007] S.11
Eine Abhängigkeit besteht insofern, dass der verwendete Prozessor eine virtuelle Speicheradressierung unterstützt, was bereits mit der IA-32-Architektur eingeführt wurde. Dies schließt natürlich nicht aus, dass die Prozessortechnik Funktionen speziell für die Parallelisierung enthalten kann, wie z.B. Multi- und Hyperthreading.
17
Siehe auch Abschnitt 4.1 auf Seite 13.
18
Vgl.: [R AUBER 2007] S.14ff.
19
Intel: MMX ab Pentium, SSE ab Pentium III
AMD: 3D-Now! ab K-6, SSE ab Athlon XP
IBM/Motorola: VMX/AltiVec ab PowerPC 7400/G4
16
-6-
beitung größerer Datenmengen nach dem SIMD-Prinzip vorgesehen sind. Diese werden in der
Regel als Technologie zur Beschleunigung von Multimedia-Applikationen angepriesen, lassen
sich aber ebenso gut für sinnvolle Rechenaufgaben nutzen.
Die letzte Stufe der Parallelisierung auf Prozessebene ermögliche die parallele Ausführung
mehrere Programme. Diese auch als Multitasking bezeichnete Konzept wird in erster Linie
durch das Betriebssystem realisiert. Wenn man vom Parallelcomputing spricht, ist in der Regel
eine solche Form der Parallelisierung von Algorithmen gemeint, wobei die Berechnung durch
mehrere, parallel ausgeführte Prozesse erfolgt. Dies resultiert nur dann in einer kürzeren Berechnungszeit, wenn für jedes Programm auch ein eigener Prozessor zur Verfügung steht.
3 Parallele Architekturen
3.1 Flynnsche Klassifizierung
Die Flynnsche Klassifizierung beschreibt vier idealisierte Klassen von Parallelrechnern basierend auf der Anzahl separater Programmspeicher, Datenspeicher und Recheneinheiten sowie
deren Abhängigkeiten untereinander. Flynn unterscheidet dabei:20
SISD Single Instruction - Single Data beschreibt einen sequentiellen Rechner, der jeweils genau eine Anweisung auf genau einem Datenelement ausführt. Es existiert keine Parallelität.
MISD Multiple Instruction - Single Data ist ein Konzept, bei dem ein Datenelement nacheinander durch verschiedene Anweisungen verarbeitet wird. Dieses Konzept ist eher theoretisch,
jedoch kann Pipelining als eine einfache Form von MISD interpretiert werden.
SIMD Single Instruction - Multiple Data entspricht der Verarbeitung mehrerer Datenelemente
durch genau eine Anweisung. Dies ist bei Vektorrechnern der Fall, aber auch bei vielen
Befehlssatzerweiterungen wie MMX oder SIMD.
MIMD Multiple Instruction - Multiple Data ist die Verarbeitung unterschiedlicher Datenelemente durch jeweils unterschiedliche Anweisungen. Dies ist bei Superskalaren Prozessoren
sowie Parallelrechnern der Fall, wobei verschiedene Funktionseinheiten oder Prozessoren
parallel verschiedene Aufgaben ausführen.
Diese Klassifizierung lässt sich auch auf die Parallelisierung von Algorithmen anwenden, wobei
jeder der als „Multiple” aufgeführten Aspekte einen potenziellen Kandidaten für eine Parallelisierung darstellt.
3.2 Speichermodelle
Aus logischer und physischer Sicht lassen sich Modelle mit gemeinsamem und verteiltem Speicher unterscheiden. Das physische Speichermodell hat großen Einfluss auf die Ausführungsgeschwindigkeit paralleler Programme, da es Latenz und Bandbreite bei Speicherzugriffen und
Kommunikationsoperationen bestimmt.
20
Vgl.: [H OFFMANN 2008] S.11f., [R AUBER 2007] S.17ff., [R AUBER 2008] S.27f.
-7-
3.2.1 Rechner mit gemeinsamem Speicher
Bei gemeinsamem Speicher existiert ein für alle Prozessoren einheitlicher Adressraum. Gemeinsamer Speicher stellt hohe Anforderungen an Synchronisations- und Konsistenzmechanismen, um einen einheitlichen Speicherinhalt zu gewährleisten, was insbesondere zur Vermeidung
von Race-Conditions bei konkurrierendem Schreib-Lese-Zugriffen notwendig ist.
Bei gemeinsamem Speicher kann der Speicherzugriff auf unterschiedliche Weise realisiert werden, abhängig davon, wie die Speicherbänke physisch organisiert sind. Der Datenaustausch
zwischen Prozessoren und Speicherbänken erfolgt über einen sogenannten Interconnect. Dieser
kann z.B. als Bus ausgelegt sein, so dass abwechselnd immer nur ein Prozessor exklusiven Zugriff auf alle Speichermodule hat. Diese Architektur ist in der Regel bei Mehrkern-Prozessoren
anzutreffen, die über einem gemeinsamen Adressbus mit dem Speicher-Controller kommunizieren. In diesem Fall ist die Zugriffsgeschwindigkeit stets gleich, daher spricht man von UMAArchitektur (Uniform Memory Access). Das Gegenstück hierzu bildet die NUMA-Architektur
(Non-Uniform Memory Access), bei der jedem Prozessor ein eigenes Speichermodul zugeordnet ist, auf das demzufolge schneller zugegriffen werden kann als auf Module anderer Prozessoren.21
Die physische Verteilung des Speichers muss nicht zwingend mit dem logischen Speichermodell übereinstimmen. So kann z.B. ein gemeinsamer Speicher auch über einen Netzwerkverbund von Rechnern mit jeweils eigenem Speicher realisiert werden, wobei Adressumsetzung
und Datenaustausch durch die jeweilige Programmierschnittstelle vorgenommen werden.
Abbildung 1: Gemeinsames Speichermodell UMA
Quelle: [BAUKE 2006] S. 11
Abbildung 2: Gemeinsames Speichermodell NUMA
Quelle: [BAUKE 2006] S. 12.
21
Vgl.: [R AUBER 2007] S. 25ff.
-8-
3.2.2 Rechner mit verteiltem Speicher
Verteilter Speicher bedeutet, dass alle Prozessoren einen eigenen, privaten Adressraum besitzen. Für den Datenaustausch muss ein Verbindungsnetzwerk existieren, und er findet nur dann
statt, wenn er explizit ausgelöst wird. Durch die entfallenden Synchronisationsmechanismen
beim Speicherzugriff ermöglicht es diese Architektur, wesentlich mehr Rechner miteinander
zu verbinden. Dies erfolgt allerdings auf Kosten der Übertragungsgeschwindigkeit von Daten
zwischen den beteiligten Rechnern, was derartige Rechner für feingranulare Probleme wenig
geeignet macht.22
Das Verbindungsnetzwerk hat erheblichen Einfluss auf die Geschwindigkeit. Die bestimmenden
Faktoren sind Bandbreite und Latenz. Es kann davon ausgegangen werden, dass mit zunehmender physikalischer Entfernung zwischen den Rechnern die Latenz steigt und die Bandbreite
sinkt.23
Weiterhin kann durch geschicktes Ausnutzen der Netzarchitektur die Geschwindigkeit von
Datenverteilungs-Operationen wie Broadcast erhöht werden, wenn Nachbarschaftsbeziehungen
ausgenutzt werden.24
Abbildung 3: Verbindungsnetzwerke
Quelle: [BAUKE 2006] S.26
Durch die Verwendung von lokal zugeordneten Cache-Speichern25 entsteht das Problem der
Cache-Kohärenz26 , da hier lokale Kopien gemeinsamer Variablen vorgehalten werden. Die Änderung einer Kopie muss nicht nur eine Aktualisierung des gemeinsamen Speichers, sondern
22
Vgl.: [R AUBER 2007] S.21ff.
Vgl.: [R AUBER 2007] Kapitel 2.5 Verbindungsnetzwerke, S. 32ff.
24
Vgl.: [S ANTORO 2007] S. 32ff.
25
Vgl.: [R AUBER 2007] S.73ff.
26
Vgl.: [R AUBER 2007] S.31., S. 84ff.
23
-9-
auch eine Aktualisierung aller anderen Kopien zur Folge haben. Die Steuerung des Caching
erfolgt durch einen eigenen Cache-Controller und ist sowohl für den Prozessor als auch aus
Programmsicht transparent.27 Die Probleme des Cachings lassen sich auf höhere Ebene übertragen, wenn es um die Entwicklung paralleler Programme und insbesondere von Bibliotheken
für das verteilte Rechnen geht, da hier ebenfalls lokale und entfernte Daten synchronisiert werden müssen.
3.3 Prozesse / Threads
Als Prozesse werden Programme bezeichnet, die zu einem gegebenen Zeitpunkt ausgeführt
werden. Ein Prozess besitzt dabei eine Prozessumgebung, die durch eine Datenstruktur im Betriebssystem realisiert wird und zur Verwaltung des Zustandes sowie der Ressourcen eines Prozesses dient. Dazu gehören z.B. die Inhalte der Prozessorregister, verwendete Speicherbereiche,
Umgebungsvariablen und geöffnete Dateien. Threads sind ebenfalls eine Form von Prozessen.
Sie unterscheiden sich von ’richtigen’ Prozessen nur dadurch, dass sie keinen exklusiven Zugriff
auf ihre Ressourcen haben, sondern diese mit anderen Prozessen, insbesondere mit dem ElternProzess, teilen. Prozesse werden unter Unix erzeugt, indem der laufende Prozess durch Aufruf
eines fork()-Kommandos in seinem aktuellen Zustand dupliziert wird. Der so entstandene KindProzess kann nun einen anderen Programmzweig ausführen als der Elternprozess, andere Daten
berechnen oder seine Prozessumgebung durch die eines anderen Programms ersetzen.
Die Unterscheidung zwischen Prozessen und Threads dient hauptsächlich dazu, den physischen
Ressourcenbedarf zu reduzieren, da beim Erzeugen eines Prozesses (theoretisch) dessen gesamte Prozessumgebung (inklusive Speicherbereiche) dupliziert werden muss, was beim Erzeugen von Threads nicht der Fall ist. Threads besitzen somit per Definition einen gemeinsamen
Adressraum mit dem Elternprozess, währen Prozesse eigene Adressräume besitzen. Allerdings
gibt es Methoden, wie z.B. Shared Memory, durch die auch Prozesse auf gemeinsame Speicherbereiche zugreifen können.
Letztendlich bilden Prozesse die Grundlage für parallele Programme. Ein paralleler Algorithmus kann also bereits durch die Methoden, die für die Verwaltung von Prozessen sowie für
die Interprozesskommunikation vom Betriebssystem bereitgestellt werden, realisiert werden.
Allerdings ist die komplette Logik zur Steuerung der einzelnen Prozesse explizit zu implementieren, was zwar sehr viele Möglichkeiten zur Optimierung der Rechengeschwindigkeit, aber
auch sehr viel Aufwand und hohe Fehleranfälligkeit bedeutet. Die Verwendung von speziell für
diesen Zweck optimierten Bibliotheken ist häufig einfacher und auch effizienter.
3.4 Client-Server-Architekturen
Client-Server-Architekturen verwenden als logische Struktur ein Stern-Modell, wobei der Server im Zentrum steht und direkte Verbindungen mit allen Clients unterhält. Parallelisierung
durch Client-Server-Architekturen werden durch das Master-Worker-Konzept umgesetzt, bei
dem der Server (Master) Rechenaufgaben oder Daten an die verbundenen Clients (Worker) sendet und diese das Resultat zurücksenden. Eine derartige Architektur lässt sich durch viele parallele Schnittstellen abbilden. Da die Abhängigkeiten zwischen den verbundenen Rechnern wenig
27
Die Existenz eines Caches macht sich durch Unterschiede in der Geschwindigkeit von Speicherzugriffen bemerkbar. Siehe dazu Kapitel 6.3.
- 10 -
komplex sind, ist diese Architektur relativ einfach zu implementieren und - sofern die physischen Ressourcen entsprechend dimensioniert sind - auch für sehr große Rechennetze höchst
effektiv, wie z.B. das Seti@Home-Projekt zeigt.
3.5 Cluster-Computing
Die Idee des Cluster-Computings besteht darin, durch Verwendung handelsüblicher Komponenten und freier Cluster-Software einen Parallelrechner mit verteiltem Speicher zu realisieren.
Durch die günstigen Kosten werden derartige Cluster häufig von Universitäten und Forschungseinrichtungen eingesetzt.
Diese auch als Beowulf-Cluster28 bezeichneten Computer gehen auf das Beowulf-Projekt der
NASA von 1994 zurück, bei dem 16 Linux-PCs mittels PVM29 zu einem Parallelrechner verbunden wurden.30
Jeder Rechner in einem Cluster wird als Knoten bezeichnet und kann wiederum selbst eine eigene parallele Architektur aufweisen. Beowulf-Cluster lassen sich sehr leicht skalieren, da hierfür
lediglich weitere Knoten an das Netzwerk angeschlossen und mit der Cluster-Software versehen werden müssen. Auf diese Weise lassen sich sogar ältere Rechner noch sinnvoll verwenden.
Das Potential dieser Architektur zeigt sich darin, dass sich in Verbindung mit HochleistungsNetzwerken und moderner Rechentechnik Cluster bauen lassen, die zu den weltweit schnellsten
Rechnern zählen. Die Tabelle 1 zeigt die weltweit fünf schnellsten Parallelrechner nach dem
Linpack-Benchmark. Diese sind natürlich nicht mehr aus einzelnen PCs zusammengesetzt, sondern bestehen aus Racks mit einer entsprechenden Anzahl von Einschüben, verwenden jedoch
handelsübliche Großserientechnik.
Die Bedeutung freier Software für das Cluster-Computing zeigt sich daran, dass viele Hersteller
auf eine Linux-Distribution zurückgreifen und diese nach Bedarf anpassen. Da der Quelltext der
Programme frei verfügbar und veränderbar ist, können so zum Beispiel optimierte Kernel (z.B.
von Cray und IBM) zum Einsatz kommen.
3.6 Grid-Computing
Als Grid-Computing31 wird eine Form des Cluster-Computings bezeichnet, bei der eine wesentlich losere Koppelung der Knoten zum Einsatz kommt. Ein derartiges Computing-Grid ist
häufig geographisch weit verteilt.
Im deutschen Sprachraum wird häufig mit dem Begriff „Grid” ein regelmäßiges Gitter assoziiert
und damit dem Grid-Computing seine Besonderheit durch Verwendung einer speziellen Topologie unterstellt. Tatsächlich weisen regelmäßige - und damit auch gitterförmige - Topologien
manche Vorteile für Routingverfahren auf, in Wirklichkeit aber stammt die Bezeichnung „Grid”
vom Englischen Begriff für „Stromnetz” (power grid) ab und weist eine beliebige Netzstruktur
im Allgemeinen - und auf das Internet im Speziellen - hin. Die Analogie zum Stromnetz ist,
dass Ressourcen im Grid transparent zur Verfügung gestellt und durch einfachen Anschluss an
das Grid abgerufen werden können.
28
http://www.beowulf.org
Siehe dazu Kapitel 10.4.
30
Vgl.: [BAUKE 2006] S.27f.
31
Vgl.:[BARTH 2006]
29
- 11 -
#
1.
2.
3.
4.
5.
Tabelle 1: Linpack Top-5 Supercomputer Stand 06/2008
System
Standort
Typ
OS
#Prozessoren
IBM
LANL
Cluster
Linux
122.400
BladeCenter
(PowerXCell 8i,
QS22/LS21
AMD Opteron
Cluster
Dual-Core)
IBM
LLNL
MPP
Suse Linux
212.992
eServer Blue
Enterprise
(PowerPC 440)
Gene Solution
Server 9 /
CNK
IBM
ANL
MPP
Suse Linux
163.840
Blue Gene/P
Enterprise
(PowerPC 450)
Solution
Server 9 /
CNK
Sun
TACC
Cluster
Linux
62.978
Microsystems
(AMD x86_64
SunBlade
Opteron Quad
x6420
Core)
Cray Inc.
ORNL
MPP
CNL
30.978
Cray XT4
(AMD x86_64
Opteron Quad
Core)
GFlop/s
1.026.000
478.200
450.300
326.000
205.000
Erläuterung:
LANL: Los Alamos National Laboratory, National Nuclear Security Administration
LLNL: Lawrence Livermore National Laboratory, National Nuclear Security Administration
ANL: Argonne National Laboratory
TACC: Texas Advanced Computing Center/Univ. of Texas,
ORNL: Oak Ridge National Laboratory
CNK: Compute Node Kernel; von IBM angepasster Linux-Kernel
CNL: Compute Node Linux von Cray Inc.
Quelle: http://www.top500.org/lists/2008/06, Stand 01.11.2008
Ein wichtiges Alleinstellungsmerkmal des Grid-Computing - vor allem im wissenschaftlichen
Bereich - gegenüber anderen Formen des Vernetzen Rechnens ist die Bereitstellung der Ressourcen auf volontärer Ebene. Dabei stellen die Teilnehmer freiwillig und kostenfrei nicht benötigte Rechenkapazität zur Verfügung, in sie ein Programm installieren, das mit niedriger Priorität ausgeführt wird und immer dann Berechnungen durchführt, wenn der Rechner nur gering
ausgelastet ist.
- 12 -
4 Parallelisierungsebenen
Parallelisierung von Programmen kann auf verschiedenen konzeptionellen Ebenen erfolgen, für
die es verschiedene Ansätze und Nebenbedingungen gibt.
4.1 Parallelität auf Instruktionsebene
Die am dichtesten an der Hardware angesiedelte Ebene, in der eine Parallelisierung möglich ist,
ist die Instruktionsparallelität. Unter bestimmten Voraussetzungen kann die Ausführung von Instruktionen eines an sich sequentiellen Programms von einer dafür ausgelegten CPU optimiert
und parallel durchgeführt werden. Dabei legt der Scheduler die Reihenfolge der auszuführenden
Instruktionen fest. Die zu parallelisierenden Instruktionen dabei lediglich auf folgende Abhängigkeiten zu prüfen:32
1. Fluss-Abhängigkeit
2. Anti-Abhängigkeit
3. Ausgabe-Abhängigkeit
Die Prüfung der Abhängigkeiten beschränkt sich jeweils auf die Verwendung der CPU-Register
durch die einzelnen Instruktionen. Besteht zwischen zwei Instruktionen keine dieser Abhängigkeiten, so können sie parallel ausgeführt werden.
Eine Fluss-Abhängigkeit besteht immer dann, wenn eine Instruktion A einen Wert in ein Register lädt, das anschließend durch Instruktion B als Operand verwendet wird. In diesem Fall
muss Instruktion A zwingend vor Instuktion B ausgeführt werden. Umgekehrt besteht eine AntiAbhängigkeit, wenn Instruktion B einen Wert in ein Register lädt, dass Instruktion A als Operand verwendet. Eine parallele oder umgekehrte Ausführungsreihenfolge führt in beiden Fällen
dazu, dass falsche Operanden verwendet werden. Wenn beide Instruktionen das selbe Register
zur Speicherung eines Ergebnisses verwenden, spricht man von Ausgabe-Abhängigkeit. Hier
kann eine Parallelisierung dazu führen, dass eine nachfolgende Operation das falsche Ergebnis
aus dem Register liest. Es tritt eine sogenannte Race-Condition ein, bei der das Ergebnis davon
abhängt, welche Instruktion schneller ausgeführt wurde.
4.2 Parallelität auf Datenebene
Datenparallelität33 liegt immer dann vor, wenn eine Operation auf mehrere, voneinander unabhängige Elemente oder Datenblöcke angewandt wird. Ein einfaches Beispiel hierfür ist eine
Schleife, die ein Array mit Werten initialisiert:
for(int i=0; i<size; i++){
array[i]=0;
}
32
33
Vgl.: [R AUBER 2007] S.120f.
Vgl.: [R AUBER 2007] S.122f
- 13 -
Es ist in diesem Fall völlig unerheblich, in welcher Reihenfolge auf die einzelnen ArrayElemente zugegriffen wird, das Resultat ist stets dasselbe. Daher ließe sich die Berechnung
in 1...size Teilschritte zerlegen, die parallel ausgeführt werden können. Ein anderes Beispiel ist
die Matrizenmultiplikation A=B*C. Hier können alle Elemente der Ergebnismatrix A unabhängig voneinander berechnet werden. Tatsächlich stellt die Implementierung von Berechnungen
als eine Reihe von Matrixoperationen eine der leichtesten Parallelisierungsmöglichkeiten dar,
da für diese Operationen bereits parallele Verfahren existieren.34
4.3 Parallelität in Schleifen
Schleifen lassen sich parallelisieren, wenn zwischen einzelnen Iterationen keine Datenabhängigkeiten bestehen. Da dadurch die Reihenfolge der Iterationen unwichtig wird, kann jede Iteration unabhängig von den anderen ausgeführt werden.35 Dieser Ansatz wird beispielsweise
durch OpenMP verfolgt.
Unter bestimmten Umständen lassen sich auch Schleifen parallelisieren, die Datenabhängigkeiten enthalten. Voraussetzung hierfür ist, dass sich die einzelnen Iterationen in Gruppen aufteilen
lassen, deren Resultate sich zu einem Gesamtergebnis zusammenfassen lassen. So kann z.B. die
folgende Schleife:
for(int i=0; i<100; i++){
summe+=array[i];
}
in Teilschleifen aufgeteilt werden, deren Einzelresultate sich durch Addition zu einem Gesamtergebnis zusammenfassen lassen:
for(int i=0; i<50; i++){
summe1+=array[i];
}
for(int i=50; i<100; i++){
summe2+=array[i];
}
summe=summe1+summe2;
Ist eine derartige Aufteilung möglich, kann die ursprüngliche Schleife ebenfalls parallel ausgeführt werden. Derartige Konstrukte werden von verschiedenen parallelen Schnittstellen unterstützt, wobei die lokalen Ergebnisvariablen (hier summe) nach Beendigung aller Schleifendurchläufe unter Verwendung eines sogenannten Reduktionsoperators zusammengefasst werden.
4.4 Parallelität auf Funktionsebene
Bei der Verwendung rein funktionaler Programmiersprachen wie z.B. Haskell lassen sich alle
Funktionsläufe parallel ausführen, da durch die Konzeption der Sprache Seiteneffekte ausgeschlossen sind. Funktionen bestehen dabei nur aus elementaren Operationen oder aus anderen
34
35
Siehe Kapitel 10.2.
Vgl.: [R AUBER 2007]S.123ff.
- 14 -
Funktionen, besitzen Eingabe- und Rückgabewerte, lösen aber keine Aktionen aus. Somit ist
ausgeschlossen, dass eine Funktion die Eingabedaten einer anderen Funktion manipuliert.
Dieses Konzept lässt sich auch auf nicht rein funktionale Programmiersprachen anwenden, sofern für den parallelisierenden Teil ausschließlich Funktionen verwendet werden, die nur lokale
Variablen verwenden. Dies entspricht der Zerlegung eines Programms sind unabhängige Teilaufgaben, sogenannte Tasks. 36
36
Vgl.: [R AUBER 2007]S.127ff.
- 15 -
5 Parallele Programmiermodelle
5.1 Darstellung
Die Darstellung der Parallelität innerhalb eines Programms kann an unterschiedlichen Stellen
erfolgen. Es lässt sich hier grob zwischen impliziter und expliziter Darstellung unterscheiden,
wobei sich jeweils weitere Detaillierungsgrade ableiten lassen.37
Implizite Darstellung
Die implizite Darstellung stellt sich aus Sicht eines Programmierers als einfachere Variante dar.
Die Programmierung erfolgt gewohnt in sequentieller Weise, die Parallelisierung wird durch
den Compiler oder durch die Eigenschaften der verwendeten Programmiersprache selbst realisiert.
Die Parallelisierung durch einen Compiler erfordert, dass der Programmablauf und die Verbindungen zwischen Variablen analysiert und auf Parallelisierbarkeit hin untersucht werden. Dies
ist ein sehr komplexer Vorgang und die erzielbaren Resultate sind in der Regel nicht sehr gut.
Daher gibt es den Ansatz, im Quellcode Hinweise für den Compiler einzubetten, die Parallelisierbare Abschnitte anzeigen. Ein derartiger Ansatz - OpenMP - ist im Kapitel 9 beschrieben.
Eine weitere Möglichkeit der impliziten Darstellung kann zum Beispiel durch die Verwendung
rein funktionaler Programmiersprachen wie Haskell geschehen. Eine Parallelisierung kann Erfolgen, indem Funktionen, die als Argumente in anderen Funktionen auftreten, parallel ausgeführt werden, da in rein funktionalen Programmiersprachen Seiteneffekte ausgeschlossen sind.
Explizite Darstellung
Die Varianten der expliziten Darstellung sind zahlreicher als die der impliziten, da es mehr Einflussmöglichkeiten auf die konkrete Umsetzung der Parallelisierung gibt, während sich die implizite Darstellung letztendlich auf eine sehr formale Beschreibung der Parallelität beschränkt.
Es lassen sich die folgenden vier Klassen unterscheiden:
1. Die Parallelität wird durch Konstrukte einer parallelen Programmiersprache (z.B. High Performance FORTRAN) oder durch Erweiterungen sequenzieller Programmiersprachen (z.B.
OpenMP) explizit dargestellt. Die konkrete Umsetzung der Parallelität erfolgt jedoch durch
den Compiler.
2. Die Zerlegung eines Algorithmus in parallele Abschnitte wird ebenfalls explizit vorgenommen, zum Beispiel durch Verwendung von mehreren Prozessen oder Threads. Die Aufteilung
auf einzelne CPUs sowie die Kommunikation zwischen den Prozessen/Threads erfolgt durch
den Compiler oder das Betriebssystem.
3. Die Zuordnung einzelner Prozesse/Threads zu den CPUs kann ebenfalls explizit vorgenommen werden. Dies ist jedoch nur in Ausnahmefällen notwendig, da die Verteilung von Prozessen auf CPUs durch das Betriebssystem vorgenommen werden sollte.
4. Die Synchronisation zwischen Prozessen sowie die Kommunikation und der Datenaustausch
können ebenfalls explizit dargestellt werden. Als Vorteil wird angesehen, dass diese Methode
die Verwendung eines Standard-Compilers erlaubt und eine sehr effiziente, auf den Anwendungsfall abgestimmte Implementierung zulässt, die dafür jedoch einen gewissen Aufwand
37
Vgl.: [R AUBER 2007]S.128ff.
- 16 -
erfordert. Als Beispiel sind Message-Passing-Konzepte wie PVM und MPI zu nennen, auf
die im Abschnitt 10.4 bzw. im Kapitel 8 genauer eingegangen wird.
5.2 Strukturierung
Die Aufgabenverteilung zwischen den Prozessen kann auf verschiedene Weise erfolgen.38 Häufig eingesetzte Modelle sind:
Master-Worker: Hier wird die Arbeit durch ein oder mehrere Kindprozesse (Slaves) ausgeführt, die von einem Hauptprozess (Master) gestartet, kontrolliert und koordiniert werden.
Pipelining: Beim Pipelining werden einzelne Prozesse so miteinander verbunden, dass die
Ausgabe eines Prozesses direkt als Eingabe des nachfolgenden Prozesses dient, wobei alle
Prozesse gleichzeitig aktiv sind.
Client-Server: Dieses Modell entspricht in seiner Funktionsweise dem umgekehrten MasterWorker-Modell, mit dem zusätzlichen Unterschied, das es sich auf einen Netzwerkverbund
bezieht und es mehrere Server geben kann. Die Rechenleistung wird durch den/die Server
erbracht und von den Clients angefordert.
5.3 Datenverteilung und Kommunikation
Die verschiedenen Arten, wie Daten zwischen Tasks ausgetauscht werden, können unter einer
Menge von Operationen zusammengefasst werden. Diese werden so oder in ähnlicher Form
durch die meisten parallelen Sprachen und Bibliotheken implementiert. Dabei lassen sich prinzipiell Einzel- und kollektive (globale) Operationen unterscheiden. Während erstere die asynchrone Kommunikation zwischen genau zwei Kommunikationspartnern beschreiben, werden
letztere von alle beteiligten Einheiten gleichzeitig ausgeführt. 39
5.3.1 Broadcast
Eine Broadcast-Operation verteilt Daten X, die einem Task P zugeordnet sind, auf alle anderen
Tasks, so dass jeder Task nach der Operation exakt die gleichen Daten besitzt.

P1 : X



P2 : []
...



Pn : []





P1 : X



P2 : X
Broadcast
=⇒
...






Pn : X







Eine Broadcast-Operation ließe sich auch mit n nachrichtenbasierten Einzeloperationen erzielen. Das Zusammenfassen der Einzeloperationen in eine einzige Broadcast-Anweisung erlaubt
es, dem zugrundeliegenden System eine effizientere Reihenfolge für das Verteilen der Daten zu
organisieren, indem die zugrundeliegende Topologie das Verteilungsschema bestimmt. Kriterien sind die notwendige Anzahl von Schritten, bis die Daten komplett verteilt sind, und die dafür
notwendige Anzahl von Nachrichten.
38
39
Vgl.: [R AUBER 2007] S.133f.
Vgl.: [R AUBER 2007] S.142ff.
- 17 -
Eine als Flooding bezeichnete Methode besteht darin, dass ausgehend von initiierenden Knoten, jeder Knoten die Daten genau einmal an alle benachbarten Knoten schickt, sobald er sie
empfangen hat. Die maximale nötige Anzahl von Schritten entspricht hier dem Durchmesser
des Graphen.40 Die Anzahl der Nachrichten hängt von der Topologie ab. Ist die Topologie beispielsweise ein kompletter Graph, genügt genau ein Schritt, da jeder Empfänger direkt erreicht
werden kann.
Abbildung 4: Effiziente Broadcast-Operation
Quelle: [BAUKE 2006] S.174
5.3.2 Scatter
Eine Scatter-Operation verteilt Daten X, die einem Prozess P zugeordnet sind, in Blöcken x :
x ∈ X gleichmäßig auf alle Prozesse, so dass jeder Prozess eine eindeutige Teilmenge der
ursprünglichen Daten hält. In der Regel wird eine direkte Zuordnung Prozess-Nummer = BlockNummer vorgenommen, aber auch anderer Verteilungsmuster sind möglich.

P1 : X



P2 : []
...



Pn : []

P 1 : x1



P 2 : x2
Scatter
=⇒
...






P n : xn











5.3.3 Gather
Die Gather-Operation entspricht einer umgekehrten Scatter-Operation. Dabei werden die Blöcke
x1 ...xn aller Prozesse im Datenbereich eines einzelnen Prozesses zusammengefasst. Sie wird
als Gather-all-Operation bezeichnet, wenn das Zusammenfassen der Blöcke für jeden Prozess
geschieht, die Prozesse also nach der Operation jeweils über alle Datenblöcke verfügen.

P1 : x 1



P2 : x 2
...



Pn : x n
40





P1 : [x1 , ..., xn ]



P 2 : x2
Gather
=⇒
...






P n : xn
Vgl.: [S ANTORO 2007] S.13f.
- 18 -








P 1 : x1



P 2 : x2
...



P n : xn





P1 : [x1 , ..., xn ]



P2 : [x1 , ..., xn ]
Gather−All
=⇒
...






Pn : [x1 , ..., xn ]







5.3.4 Reduktion
Eine Reduktions- oder Akkumulations-Operation führt eine Funktion f() über den Daten aller
Prozesse aus und stellt das Ergebnis in einem Prozess zur Verfügung. Typischerweise werden
für f() arithmetische Funktionen (Summe, Produkt, Durchschnitt) oder Vergleichoperationen
(größtes Element, kleinstes Element) implementiert.

P 1 : x1



P 2 : x2
...



P n : xn





P1 : f (x1 , ..., xn )



P 2 : x2
Akkumulation
=⇒
...






P n : xn







Der Sinn kollektiver Reduktionsfunktionen liegt ebenfalls in der Erzielung einer höheren Geschwindigkeit durch Verringerung der Anzahl notwendiger Kommunikationsschritte gegenüber
der entsprechenden Anzahl von Einzeloperationen oder Synchronisierungsoperationen. Eine
Akkumulationsfunktion muss sowohl kommutativ als auch assoziativ sein, da die Reihenfolge
der Einzeloperationen nicht vorhergesagt werden kann.
Abbildung 5: Effiziente Akkumulations-Operation
Quelle: [BAUKE 2006] S.185
5.4 Synchronisation
Eine parallele Ausführung bedeutet nicht, dass Programme auch synchron ausgeführt werden.
Neben unterschiedlichen Prozessorgeschwindigkeiten und der jeweiligen Prozessorauslastung
gibt es eine Reihe von Störfaktoren, die Einfluss auf die tatsächliche Ausführungsgeschwindigkeit haben. Daher kann man nicht davon ausgehen, dass sich eine gemeinsame Ressource - wie
zum Beispiel ein Speicherinhalt - in einem stets definierten Zustand befindet. Wenn ein Prozess
einen Abschnitt erreicht, der einen definierten Zustand voraussetzt, muss dieser zunächst hergestellt werden. Dies wird durch Synchronisationsmechanismen erreicht, die dafür sorgen, dass
sich die zu synchronisierende Ressource zu einem bestimmten Zeitpunkt für jeden beteiligten
- 19 -
Prozess in einem definierten Zustand befindet. Beispielsweise stellt die Speichersynchronisation identische Speicherinhalte für alle Prozesse sicher, eine Ablaufsynchronisation sorgt dafür,
dass alle parallelen Prozesse die exakt gleiche Stelle im Programmtext erreicht haben.
5.4.1 Kritische Abschnitte
Bei Race-Conditions ist der Zustand einer Ressource davon abhängig, welche Prozesse in welcher Reihenfolge darauf zugreifen. Insbesondere nicht-atomare Anweisungen, die während der
Ausführung unterbrochen werden können, sind dafür anfällig. Solche Programmabschnitte werden als „Kritische Abschnitte” bezeichnet. Für die Koordinierung der Prozesse sind eine Reihe
von Verfahren bekannt.41 Parallele Sprachen und Bibliotheken stellen in der Regel Möglichkeiten zum Kennzeichnen kritischer Abschnitte bereit, so dass der Programmierer keine eigene
Zugriffssteuerung implementieren muss. Probleme bereiten nur solche kritischen Abschnitte,
die vom Programmierer nicht als kritisch erkannt werden, da die dadurch resultierenden Programmfehler sporadisch, an verschiedenen Programmstellen und schlecht reproduzierbar auftreten können, was den Anschein zufälliger Fehler erzeugt. Weiterhin kann es vorkommen, dass
optimierende Compiler Code erzeugen, der nicht der Ausführungsreihenfolge der Anweisungen
im Quelltext entspricht.
5.4.2 Barrieren
Barrieren sind eine Methode der Ablaufsynchronisation. Sie stellen sicher, dass sich alle Prozesse zu einem bestimmten Zeitpunkt an der selben Stelle im Programmtext - der Barriere - befinden. Nachdem alle Prozesse die blockierende Barriere erreicht haben, werden sie synchron
fortgesetzt. Dabei kann es vorkommen, dass einzelne Prozesse sehr lange auf andere warten
müssen. Nicht-blockierende Barrieren dagegen erlauben es einem Prozess, mit der Ausführung
von Programmcode fortzufahren, der nicht von anderen Prozessen abhängig ist, sofern diese
mindestens eine bestimmte Stelle im Programmcode erreicht haben. Auf diese Weise kann ein
Performancegewinn erzielt werden, da die Wartezeiten verkürzt werden können.
5.4.3 Locks
Der konkurrierende Zugriff auf Ressourcen kann durch Locking-Mechanismen gesteuert werden. Locks stellen eine Möglichkeit dar, Race-Conditions zu vermeiden, da der Zugriff auf
gemeinsame Ressourcen exklusiv erfolgt. Anders als kritische Abschnitte können Locks über
verschiedene Programmteile hinweg aufrecht erhalten werden. So kann an einer Stelle ein Lock
erzeugt und später an einer anderen Stelle wieder freigegeben werden. Die Verantwortung für
die Freigabe eines Locks liegt allein beim Programmierer, daher ist die Verwendung potentiell
Anfällig für Deadlock-Situationen.
41
Vgl. hierzu: [C ARVER 2006] S. 46ff.
- 20 -
6 Einflussfaktoren Paralleler Programme
6.1 Parallele Skalierbarkeit
Durch Parallelisierung soll in der Regel ein Geschwindigkeitszuwachs in der Form erzielt werden, dass pro Zeiteinheit mehr Rechenoperationen durchgeführt werden. Ähnlich der Frage
nach den Grenzkosten oder dem Grenznutzen in der BWL und VWL stellt sich auch hier die
Frage, welcher Aufwand - vor allem in Form zusätzlicher Rechner oder Prozessoren - für eine
Parallelisierung notwendig ist und in welchem Verhältnis dieser zum erzielten Geschwindigkeitsgewinn steht.
6.1.1 Speedup
Geht man davon aus, dass ein parallelisierbares Programm auf einem Prozessor die Rechenzeit
T1 benötigt, so ist die Rechenzeit auf p Prozessoren im optimalen Fall T1 /p. Parallelisierbare
Abschnitte machen in der Regel nur einen Teil des gesamten Programms aus, so dass ein bestimmter Anteil der Rechenzeit immer für einen sequentiellen Teil verbraucht wird, der konstant
ist. Durch Erhöhung der Parallelität nimmt im parallelen Teil die Rechenzeit pro Prozessor ab,
was beabsichtigt ist, wodurch der relative Anteil der sequentiellen Abschnitte größer wird. Das
führt dazu, dass eine obere Grenze für den erzielbaren Geschwindigkeitszuwachs durch Hinzunahme eines weiteren Prozessor existiert. Dieser Effekt wird nach seinem Entdecker Gene
Amdahl als Amdahl’sches Gesetz42 bezeichnet.
Ist für Eingaben der Größe n die Rechenzeit auf einem Prozessor T1 (n) und die Rechenzeit
auf p Prozessoren Tp (n), so ergibt sich der theoretisch erzielbare Geschwindigkeitszuwachs
(Speedup) aus:43
Sp (n) =
T1 (n)
Tp (n)
(1)
Der höchste theoretisch erzielbare Speedup ist Sp (n) = p, was einem linear skalierenden
Programm entspricht. In der Praxis kann jedoch ein Speedup Sp (n) > p auftreten, was als
superlinearer Speedup bezeichnet wird. Dies kann dadurch eintreten, dass die Problemgröße
bzw. Datenmenge pro Prozessor so klein wird, dass geschwindigkeitssteigernde Effekte durch
Caching-Mechanismen eintreten 44 . Es ließe sich auch umgekehrt argumentieren, nämlich dass
im Regelfall geschwindigkeitssenkende Effekte durch Verwendung langsamerer Komponenten
außerhalb des schnellen Cache bei zu großen Datenmengen die Ursache sind.
6.1.2 Amdahl’sches Gesetz
Amdahl nimmt einen Faktors f für den seriellen Anteil45 eines Programmlaufs an, so dass sich
die parallele Rechenzeit aus der Summe des nicht parallelisierbaren Anteils und des paralleli42
Vgl.: [C HAPMAN 2008] S. 33f., [C HANDRA] S. 173f., [BAUKE 2006] S.10ff., [R AUBER 2008] S. 170ff.,
[R AUBER 2007] S.37f., [H OFFMANN 2008] S.13ff., [E M K ARNIADAKIS 2000] S.39f.
43
Vgl.: [R AUBER 2007] S.168
44
Vgl.: [R AUBER 2008] S.37, [R AUBER 2007] S.169
45
In der Literatur wird auch statt des seriellen Anteils f der parallele Anteil verwendet. Der Teiler ist dann entsprechend umzustellen.
- 21 -
sierbaren Anteils von T1 ergibt:
1−f
T1 (n)
p
Nach obiger Formel ergibt sich der nach Amdahl erzielbare Speedup als eine von n unabhängige
Größe, was als Amdahl’sches Gesetz bezeichnet wird:46
Tp (n) = f ∗ T1 (n) +
Sp (n) =
T1 (n)
{f ∈ R|0 ≤ f ≤ 1}{p ∈ N|p > 0}
f ∗ T1 (n) + 1−f
T1 (n)
p
S(p) = 1
f+
1−f
p
(2)
Der in Abhängigkeit des Faktors f erzielbare Speedup ist in Abbildung 6 dargestellt47 . Im optimalen Fall einer vollständigen Parallelisierbarkeit mit f=1.0 skaliert ein Programm linear, während bereits für f=0.95 der Zuwachs sehr schnell und deutlich stagniert. In dieser Berechnung ist
noch nicht berücksichtigt, dass mit höherer Parallelität der zusätzliche Aufwand für die Erzeugung von Threads/Prozessen sowie für die Synchronisation und den Datenaustausch ebenfalls
ansteigt.
Abbildung 6: Parallele Skalierbarkeit nach Amdahl
Quelle: eigene Darstellung.
6.1.3 Gustafson-Gesetz
Neben dem hier beschriebenen Amdahl’schen Gesetz existieren eine Reihe von Varianten, die
weitere Faktoren berücksichtigen. Dazu gehören Beispielsweise der in Abhängigkeit von p stei46
47
Vgl.: [R AUBER 2007] S.170, [R AUBER 2008] S.38, [E M K ARNIADAKIS 2000] S.79f.,
Ein Rechner mit 16 Rechenkernen stellt dabei eher die untere Grenze heutiger Parallelrechner dar. Entsprechende
Boards mit 4 Sockeln sowie Quad-Core-Prozessoren sind im normalen Handel erhältlich. Ein heutiger ’kleiner’
Supercomputer ist Beispielsweise der Cray CX1, der noch unter einem Schreibtisch Platz findet, und bis zu 16
Quad-Core CPUs enthält. Ein Cray XMT enthält bis zu 8096 CPUs. (http://www.cray.com)
- 22 -
gende Kommunikationsaufwand, wodurch bei großen p eine weitere Erhöhung zu einer Verringerung des Speedup führt, wie in Abbildung 7 dargestellt.
Abbildung 7: Parallele Skalierbarkeit nach Amdahl
Quelle: [BAUKE 2006] S.12
Eine weitere Variante besteht darin, den parallelisierbaren Anteil auch in Abhängigkeit von der
Eingabegröße zu betrachten. Dabei wird eine konstante Ausführungszeit für den sequentiellen
Teil angenommen, wodurch bei steigender Eingabegröße der sequentielle Anteil abnimmt. Das
Amdahl’sche Gegenstück hierfür ist eine monoton sinkende Funktion f (p, n) > 0 anstelle des
Faktors f.
In der Betriebswirtschaft existieren prinzipiell zwei Möglichkeiten, um bei konstanten Verkaufszahlen den Gewinn und somit die Effizienz des Kapitaleinsatzes zu erhöhen: eine Erhöhung der Preise oder eine Senkung der Kosten. Um die Effizienz von Parallelisierungsmaßnahmen zu erhöhten, gibt es ebenfalls zwei grundsätzliche Möglichkeiten: Verringerung der
Rechenzeit durch mehr Prozessoren oder Erhöhung der Eingabegröße.
Die Verringerung der Rechenzeit durch steigende Prozessorzahlen ist durch die bisher beschriebenen Gesetzmäßigkeiten limitiert. Weiterhin stellt sich die Frage, ob eine signifikante Verringerung der Rechenzeit den dafür notwendigen zusätzlichen Aufwand überhaupt rechtfertigt,
was in der Regel nur auf sehr zeitkritische oder aber sehr langwierige Berechnungen zutreffen
dürfte. Ein alternatives Ziel stellt eine bei etwa gleichbleibender Rechenzeit erzielbare Erhöhung der Eingabegröße dar. Als Beispiel soll hier eine Anfrage an eine Internet-Suchmaschine
dienen. Wenn die Antwortzeit in einem akzeptablen Bereich von z.B. wenigen Sekunden liegt,
hat eine weitere Verringerung der Antwortzeit im Verhältnis zur damit verbundenen Erhöhung
der Rechenkapazität nur wenig Sinn, wohl aber die Vergrößerung der Suchraumes, um die Trefferzahl zu erhöhen oder die Trefferqualität zu verbessern.
Beim Gustafson-Gesetz48 wird die Eingabegröße berücksichtigt, indem von einer gegebenen
parallelen Laufzeit Tp (n) für ein Problem ausgegangen und diese in Relation zur hypothetischen
sequentiellen Laufzeit T1 (n) = f Tp (n) + p(1 − f )Tp (n) gesetzt wird. In Anlehnung an die
bisherige Notation ergibt sich daraus der skalierte Speedup:
Sp (n) =
48
f Tp (n) + p(1 − f )Tp (n)
= f + p(1 − f ) = p + (1 − p)f
Tp (n)
Vgl.: [R AUBER 2007] S. 171f., [R AUBER 2008] S. 27f., [BAUKE 2006] S.16, [H OFFMANN 2008] S.16f.
- 23 -
(3)
In Abbildung 8 ist der Unterschied zwischen beiden Ansätzen für f=0.05 beispielhaft dargestellt. Der fast lineare Speedup nach Gustafson beruht darauf, dass für ein gegebenes paralleles
Problem die entsprechende rein sequentielle Rechenzeit durch die damit linear steigende Eingabegröße zunimmt, während Amdahls Ansatz ein gegebenes Problem auf einem einzelnen
Rechner durch Parallelisierung schneller zu lösen versucht, was durch den stets geringer werdenden parallelen Anteil eine asymptotische Funktion zur Folge hat.
Abbildung 8: Speedup Amdahl vs. Gustafson
Quelle: eigene Darstellung.
6.1.4 Karp-Flatt-Metrik
Nun lässt sich der Faktor f nur sehr schwer anhand theoretischer Überlegungen oder Quelltextanalysen bestimmen. Durch Messung lässt sich f jedoch für ein bestimmtes Problem und
Programm empirisch feststellen. Ein solches Maß ist die Karp-Flatt-Metrik. Dabei wird für
mehrere p der Wert von T(p) experimentell bestimmt. Anschließend kann durch Amdahl’s Gesetz der entprechende empirische Anteil f bestimmt werden:49
f=
1/Sp (n) −
1−
1
p
1
p
Anhand des Verhaltens dieses Wertes zu p lässt sich aussagen, ob der Speedup eher vom sequentiellen Anteil oder vom durch die Parallelisierung induzierten Rechenaufwand (Overhead)
bestimmt wird. Letzteres ist daran zu erkennen, dass f mit steigender Prozessorzahl zunimmt,
wodurch auch der Speedup langsamer steigt, als durch Amdahl’s Gesetz beschrieben.
Die Effizienz der Parallelisierung lässt sich aus dem Verhältnis zwischen Prozessor-Anzahl p
und dem dadurch erzielten Speedup S(p) bestimmen:
49
Vgl.: [BAUKE 2006] S.16f.
- 24 -
E(p) =
S(p)
T (1)
=
pT (p)
p
6.2 Load Balancing und Scheduling
Die Aufteilung einer Rechenaufgabe in Teilaufgaben und deren Zuordnung zu Prozessen kann
auf verschieden Weise erfolgen. Dies wird als Scheduling bezeichnet. Die Aufteilung erfolgt
anhand einer Strategie mit dem Ziel, für eine Aufgabe eine möglichst effiziente Ressourcennutzung zu erzielen. Es lassen sich statische und dynamische Scheduling-Strategien unterscheiden.
Die Verwendung einer bestimmten Strategie kann Auswirkungen auf die Gestaltung des Programms haben.
Die verwendete Scheduling-Strategie hat Auswirkungen auf die Ausführungsgeschwindigkeit
paralleler Programme. Insbesondere statische Strategien sind anfällig für Verzögerungen bei
einzelnen Prozessen. Die gesamte Abarbeitungszeit ist dabei vom langsamsten Prozess abhängig. Dies führt dazu, dass eine sehr starke Parallelisierung oft nicht den erwarteten Effekt hat.
Besonders wenn die Anzahl der Prozesse sich der Zahl verfügbarer Prozessoren annähert, steigt
die Wahrscheinlichkeit, dass einzelne Prozesse sich Ressourcen mit anderen Prozessen teilen
müssen, wodurch diese eine längere Zeit zur Beendigung ihrer Aufgabe benötigen.
Dynamische Strategien oder das Master-Worker-Modell haben den Vorteil, dass Rechenaufgaben an die gerade verfügbaren Prozesse / Prozessoren verteilt werden. Verzögerungen einzelner Prozesse können leicht kompensiert werden, indem schnellere Prozesse zusätzliche Anteile
übernehmen.50
6.3 Lokalität
Unter Lokalität versteht man, dass die zu verarbeitenden Daten sich im direkten Zugriff des
Verarbeiters befinden. Tun sie es nicht, müssen sie erst dorthin übertragen werden, was Zeitaufwändig ist. Diese Beschreibung ist sehr allgemein und kann sich auf unterschiedliche Bereiche
bzw. Ebenen beziehen. Je höher diese Ebene angesiedelt ist, desto größer ist der Einfluss der
Lokalität auf die Rechengeschwindigkeit, da der Aufwand zur Datenübertragung steigt und die
Übertragungsgeschwindigkeit in der Regel sinkt.
In einem Computercluster aus mehreren Knoten und einem Verbindungsnetzwerk sind Daten
lokal, wenn sie im Speicher des Knoten verfügbar sind, der sie Verarbeitet. Auf der Ebene eines Parallelrechners mit mehreren Prozessoren sind Daten lokal, wenn sie sich im durch den
Prozessor adressierbaren physikalischen Speicher befinden, auf Ebene eines Prozessors oder
Prozessorkernes dann, wenn sie sich in dessen Cache oder gar in den Prozessorregistern befinden.
Der Einfluss der Lokalität lässt sich bereits in seriellen Programmen feststellen. Die Geschwindigkeit, mit der auf Daten zugegriffen werden kann, hängt sehr stark davon ab, ob der Prozessor
diese erst aus dem relativ langsamen Hauptspeicher lesen muss, oder ob die Daten bereits im
deutlich schnelleren 2nd-Level oder gar 1st-Level Cache zur Verfügung stehen. Caching erfolgt
in der Regel blockweise, so dass beim erstmaligen Zugriff auf eine Speicherzelle im RAM auch
50
Zum Vergleich der Strategien siehe Kapitel 11.2.
- 25 -
Abbildung 9: Row- vs. Column-first Ordering
Quelle: Eigene Darstellung.
benachbarte Bytes in den Cache übertragen werden. Im Zusammenspiel damit, wie ein Compiler Daten - insbesondere Arrays - im Hauptspeicher ablegt, hat dies Auswirkungen auf die
Ausführungsgeschwindigkeit des Programms.
Im C-Standard wird Row-First-Ordering verwendet.51 Das bedeutet, die Indizes [0][0] und
[0][1] liegen im Speicher an hintereinander folgenden Adressen. Folglich müssen Matrizenoperationen, die Zeilenweise durchlaufen werden, schneller sein als solche, die Spaltenweise
arbeiten. Wie groß der Unterschied tatsächlich ist, verdeutlicht die Abbildung 9. Es wurde die
Zeit gemessen, um die Summe aller Matrizenelemente für quadratische Matrizen verschiedener
Größen zu berechnen.
6.4 Speichersynchronisation
Die Einhaltung einer identischen, eindeutigen Sicht aller Prozesse auf den gemeinsamen Speicher wird durch Synchonisierungsmechanismen realisiert. Es lassen sich verschieden strikte
Modelle unterscheiden:52
• Das Modell mit den meisten Einschränkungen ist das sequentielle Konsistenzmodell. Es
stellt sicher, dass Veränderungen gemeinsamer Variablen unmittelbar und in exakt der gleichen zeitlichen Reihenfolge, in der sie von allen Prozessen ausgeführt werden, für alle Prozesse sichtbar sind. Dies bedeutet im Prinzip eine sofortige Synchronisierung der betroffenen
Speicherinhalte nach einem Schreibzugriff.
• Abgeschwächte Konsistenzmodelle erlauben beschränkt inkohärente Speicherzustände für
bestimmte Programmabschnitte was sowohl den Speicherinhalt als auch die Zugriffsreihenfolge betrifft. Da es nicht immer notwendig ist, stets eine kohärente Sicht auf den Speicher zu
51
52
Vgl. hierzu: [B RANDS 2005] Kapitel 6 - Mehrdimensionale Felder, S. 300ff.
Für eine detaillierte Beschreibung siehe [R AUBER 2007] S.92ff.
- 26 -
gewährleisten, z.B. wenn nacheinander mehrere Schreibzugriffe erfolgen und nur der letzte
Zugriff das endgültige Resultat darstellt, kann so die Ausführungsgeschwindigkeit von Programmen deutlich erhöht werden. Die Synchronisierung erfolgt in solchen Fällen explizit
nach Beendigung des jeweiligen Programmabschnitts.
Parallele Bibliotheken und Programmiersprachen unterstützen in der Regel verschiedene Konsistenzmodelle. Da diese direkte Auswirkungen auf die Funktionsweise und Ausführungsgeschwindigkeit eines parallelen Programms haben, ist dieses Thema auch aus Sicht eines Programmierers relevant.
- 27 -
Teil II
Bibliotheken und Systeme
Es existieren eine Reihe von Programmbibliotheken und Compilern, die das entwickeln Paralleler Programme unterstützen sollen. Viele davon sind GPL-Lizensiert und frei verfügbar. In
diesem Teil wird auf konkrete Implementierungen und Schnittstellen für das entwickeln paralleler Algorithmen unter C/C++ eingegangen.
Development of parallel programs is supported by various programming libraries, interfaces
and compilers. Many of them are license under the GPL Public License, which makes them
freely available. This chapter provides an introduction to interfaces and programming libraries
for the C/C++ programming language.
- 28 -
7 Unified Parallel C
Unified Parallel C (UPC)53 erweitert ANSI C um Konstrukte, die einen Einsatz auf HochleistungsParallelrechnern ermöglichen54 . Dafür wurden der Sprache C folgende Erweiterungen hinzugefügt:
• Ein explizites paralleles Ausführungsmodell
• Ein gemeinsamer Adressraum
• Synchronisationsmechanismen
• Speicherkonsistenzmodelle
• Eine angepasste Speicherverwaltung
Die Spezifikation von UPC wurde von einem Konsortium aus Unternehmen, Bildungsträgern
und Regierungsorganisationen der USA entwickelt. Die Zusammensetzung des Konsortiums ist
der Übersicht im Anhang A.2 zu entnehmen.
Um UPC nutzen zu können, sind spezielle Compiler erforderlich. Diese basieren häufig auf
quelloffenen C/C++ Compilern, die um UPC-Konstrukte erweitert wurden. In Tabelle 2 auf
Seite 82 sind einige aktuell verfügbare Compiler und die jeweils unterstützten Betriebssysteme
und Rechnerarchitekturen aufgeführt. Der in dieser Arbeit verwendete Compiler ist der GCC
UPC in der Version 4.0.3.
7.1 Verfügbarkeit
Ein UPC-Compiler für Linux-Systeme basierend auf dem GCC ist im Internet verfügbar und
kann kostenfrei heruntergeladen werden. Neben bereits übersetzten Paketen für verschiedene
Prozessoren und Distributionen ist auch ein Quellpaket vorhanden.55
Nach dem Entpacken der Quellen ist ein separates Verzeichnis anzulegen, in dem die Übersetzung erfolgt, typischerweise “build” genannt. Die Konfiguration der Make-Umgebung erfolgt
mittels des mitgelieferten configure-Skriptes. Um nicht in Konflikt mit existierenden GCCCompilern zu kommen, ist es empfehlenswert, den UPC in einem separaten Verzeichnis zu
installieren, z.B. /usr/local/upc. Die Befehle zum Übersetzen der Quellen und anschließendem
Installieren sind:
$ t a r −x z f upc − 4 . 0 . 3 . 5 . s r c . t a r . gz && cd upc − 4 . 0 . 3 . 5 && m k d i r
b u i l d && cd b u i l d
$ . . / c o n f i g u r e −− p r e f i x = / u s r / l o c a l /
$ make && make i n s t a l l
7.2 Parallelisierungskonzept
Die Parallelisierung mittels UPC erfolgt durch Threads. Sie unterscheidet sich jedoch von herkömmlicher Thread-Programmierung dadurch, dass der Code für die Erzeugung der Threads
durch den UPC-Compiler in das Programm integriert wird und nicht durch den Programmierer.
53
Vgl.: [C HAUVIN 2008], [R AUBER 2007] S.371ff.,
UPC Homepage: http://upc.gwu.edu, Stand 01.12.2008
55
http://www.intrepid.com/upc/downloads.html
54
- 29 -
Die Anzahl zu verwendender Threads kann sowohl zur Übersetzungszeit als auch zur Laufzeit
festgelegt werden.
Um Berechnungen parallel durchzuführen, wird ein Schleifenkonstrukt upc_forall verwendet:
upc_forall ( expr1; expr2; expr3; affinity)
Die Syntax entspricht exakt der C-Syntax für for-Schleifen, mit dem Unterschied, dass ein
vierter Parameter “affinity” existiert, der angibt, durch welchen Thread die aktuelle Iteration
durchgeführt wird. Dies kann entweder durch die Angabe der Thread-Nummer oder einer Speicheradresse geschehen. In letzterem Fall wird die Iteration durch den Thread durchgeführt, in
dessen Adressraum die Speicheradresse liegt. Dies ist insbesondere dann nützlich, wenn über
ein verteiltes Array iteriert wird und die Lokalität verschiedener Array-Elemente ausgenutzt
werden soll.
7.3 Speichermodell
UPC implementiert ein Modell mit verteiltem Speicher, wobei dieser sowohl gemeinsam (shared) als auch einzeln (private) genutzt werden kann. Der private Speicher eines Threads liegt
ausschließlich in seinem eigenen Adressraum und ist nur für ihn selbst sichtbar. Privater Speicher liegt immer dann vor, wenn Speicher in herkömmlicher C-Notation alloziert wird, so das
es hier keinen Unterschied zur nicht-parallelen Programmierung gibt.
Gemeinsam genutzter Speicher wird durch Verwenden des neuen Schlüsselwortes shared erzeugt. Auf eine als shared deklarierte Variable kann von allen Threads aus zugegriffen werden.
Folgendes Listing zeigt einige Möglichkeiten, private und gemeinsame Variablen zu definieren:
int local_i;
shared int global_i;
shared int * ptr1;
// private Variable
// gemeinsame Variable
// privater Zeiger auf gemeinsame
Variable
shared int * shared ptr2; // gemeinsamer Zeiger auf gemeinsame
Variable
Eine interessante Variante bilden verteilte Arrays. Die Deklaration erfolgt folgendermaßen:
shared [block_size] typ name[size];
Ein derart erzeugtes Array wird in Blöcken der Größe block_size komplett auf den gemeinsamen
Speicher verteilt. Wird das Array anschließend durch eine upc_forall-Schleife verarbeitet, kann
auf diese Weise bereits eine Zuordnung des verwendeten Speichers zu den einzelnen Threads
vorgenommen werden, so dass Zugriffe auf die Array-Elemente lokal erfolgen können. Anderenfalls würden Zugriffe auf Elemente, die sich im Speicherbereich eines anderen Threads
befinden, in entfernte Zugriffe umgewandelt werden müssen, was einen gewissen Kommunikationsaufwand erfordert und daher langsamer ist.
Standardmäßig wird eine Blockgröße von 1 angenommen. Eine Blockgröße von 0 würde dazu
führen, dass alle Elemente im Speicherbereich des ersten Threads abgelegt werden. Im Folgenden sind zwei Beispiele für die Verteilung von Elementen bei verschiedenen Blockgrößen
dargestellt:
- 30 -
shared int a[10];
Thread 1
a[0]
a[4]
a[8]
Thread 2
a[1]
a[5]
a[9]
Thread 3
a[2]
a[6]
Thread 4
a[3]
a[7]
Thread 2
a[2]
a[3]
Thread 3
a[4]
a[5]
Thread 4
a[6]
a[7]
shared [2] int a[10];
Thread 1
a[0]
a[1]
a[8]
a[9]
Verteilte Arrays müssen global und dürfen nicht dynamisch sein. Daher ist das Anwendungsfeld dieser einfachen Notation von vornherein beschränkt. Weiterhin ist die Verwendung lokaler Variablen, z.B. in Funktionsrümpfen, für die Parallelisierung ausgeschlossen. Es kann zwar
verteilter, dynamisch allozierter Speicher erzeugt werden, jedoch muss dieser dann durch entsprechende Speichermanipulationsroutinen explizit gefüllt werden.
Diese Beschränkung lässt sich durch Verwendung dynamisch allozierten verteilten Speichers
umgehen. UPC bietet dazu verschiedene malloc-ähnliche Funktionen.
UPC stellt zwei Konsistenzmodelle bereit: strict und relaxed. Das strict-Modell stellt sicher,
dass vor Speicherzugriffen eine Synchronisation stattfindet. Ebenso werden Optimierungen seitens des Compilers unterbunden, welche die Reihenfolge von Zugriffsoperationen auf unabhängige Speicherbereiche verändern.
Das relaxed-Modell dagegen erlaubt den jederzeitigen Speicherzugriff. Synchonisierung und
Locking-Mechanismen müssen dabei explizit im Programmtext implementiert werden.
Das verwendete Konsistenzmodell kann entweder global durch das Einbinden der HeaderDateien upc_strict.h oder upc_relaxed.h erfolgen, durch #pragma-Direktiven für einzelne Programmabschnitte festgelegt, oder direkt bei der Variablendeklaration bestimmt werden.
#include <upc_strict.h>
#include <upc_relaxed.h>
strict shared int i;
strict relaxed int j;
{
#pragma upc strict
// code-block
- 31 -
}
#pragma upc relaxed
{
#pragma upc relaxed
// code-block
}
7.4 Synchonisation
Neben den beschriebenen Konsistenzmodellen stellt UPC für die Synchronisation weitere Mechanismen bereit.
Locks Locking-Mechanismen lassen sich auf Daten-Ebene verwenden. Dabei können gemeinsame Variablen durch den Aufruf von upc_lock() für den Zugriff durch andere Threads gesperrt und später durch upc_unlock() wieder freigegeben werden.
Barrieren UPC stellt Barrier-Konstrukte bereit, mit denen sichergestellt werden kann, dass zu
einem Zeitpunkt alle Threads eine bestimmte Stelle im Programmcode erreicht haben. Es
werden blockierende und nicht-blockierende Barrieren unterschieden.
Blockierende Barrieren werden über das upc_barrier-Kommando in den Programmcode eingefügt. Der Programmablauf wird an dieser Stelle unterbrochen, bis alle anderen Threads die
selbe Stelle erreicht haben.
7.5 Globale Operatoren
UPC unterstützt Datenverteilungs- und Sammeloperationen, Reduktionsoperationen sowie Einund Ausgabeoperationen. Diese werden als Funktionsaufruf an entsprechender Stelle im Programmtext eingefügt.
Zur Datenverteilung sind Scatter-, Gather- sowie Permutationsverfahren implementiert. Als Reduktionsoperationen stehen arithmetische binäre Operatoren sowie Sortieroperationen zur Verfügung. Der koordinierte Zugriff auf Dateien und das Lesen und Schreiben aus bzw. in gemeinsame Speicherbereiche wird unterstützt.
- 32 -
8 Message Passing Interface
Message-Passing ist ein Konzept für Parallelrechner mit verteiltem Speicher. Die logische Sicht
ist, dass Prozesse nur Zugriff auf ihren lokalen Speicher haben, aber miteinander durch das Versenden von Nachrichten kommunizieren können, unabhängig davon, auf welchem Prozessor
oder Rechner sie gerade ausgeführt werden. Ein derartiges Modell wird beispielsweise durch
MPI oder PVM dargestellt und lässt sich auf verschiedene physische Rechnerarchitekturen abbilden.
Abbildung 10: Message-Passing
Quelle: [BAUKE 2006] S.45
MPI ist eine Spezifikation für die Abbildung eines Modells nach dem Message-Passing-Prinzip.
Es ist ein Standard, der Schnittstellen für die Sprachen C und Fortran definiert. Die Erweiterung
MPI-2 stellt Schnittstellen auch für C++ bereit.
Auf MPI-Standards wird hauptsächlich durch die Bezeichnungen MPI-1 und MPI-2 verwiesen.
MPI-I entspricht dabei der Version MPI 1.2 von 1995. MPI-2 ist eine spätere abwärtskompatible
Entwicklung, die MPI-1 um neue Funktionen erweitert. Dieser Standard wird nur von sehr
wenigen MPI-Implementierungen vollständig unterstützt.
MPI ist extrem umfangreich und es existieren von vielen Funktionen mehrere Varianten, die
sich nur in Details voneinander unterscheiden. Daher kann MPI hier nur ansatzweise dargestellt
werden. Für Details sei auf die Literatur verwiesen.56
56
Vgl. hierzu: [E M K ARNIADAKIS 2000]S.80ff., [R AUBER 2007] S.207ff, [BAUKE 2006] S.167ff., [G ROPP 2007]
- 33 -
8.1 Verfügbarkeit
Da MPI keine spezifische Implementation beinhaltet, existieren verschiedene MPI-Bibliotheken, die meist auf eine spezielle Rechnerarchitektur angepasst sind. Frei verfügbare Implementierungen sind z.B. Open-MPI, LAM-MPI und das vom Argonne National Laboratory stammende MPICH/MPICH2. Daneben existieren noch weitere proprietäre und teils optimierte Lösungen wie z.B. die Intel MPI Library.
8.2 Parallelisierungskonzept
Eine MPI-Anwendung besteht stets aus einer konstanten Anzahl von Prozessen. Diese kann
beim Programmstart variabel festgelegt werden, lässt sich jedoch zur Laufzeit nicht mehr verändern. Häufig geschieht das Aufrufen eines MPI-Programms durch ein separates Startprogramm,
dass von der verwendeten Implementierung zur Verfügung gestellt wird und das entsprechend
viele Instanzen des eigentlichen MPI-Programms aufruft.
Da MPI auch in heterogenen Rechnerumgebungen ausgeführt werden kann, müssen alle Nachrichten auf MPI-Datentypen abgebildet werden, die von MPI bei Bedarf in die entsprechende
Darstellung konvertiert werden. Um komplexere Datenstrukturen nicht Wert für Wert übertragen zu müssen, können eigene MPI-Datentypen und Strukturen definiert werden.
8.3 Speichermodell
Einem MPI-Programm sind dabei lokal Daten zugeordnet, auf die frei zugegriffen werden
kann. Durch das Verschicken von Nachrichten kann ein MPI-Programm mit anderen MPIProgrammen kommunizieren und so Daten austauschen. MPI definiert verschiedene Funktionen, die Verteil- und Sammeloperationen durchführen. Dabei ist es nicht möglich, dass ein Prozess unbemerkt die Daten eines anderen Prozesses verändern kann, da alle Kommunikationsoperationen stets, jedoch nicht zwingend synchron, durch alle beteiligten Prozesse aufgerufen
werden müssen.
8.4 Synchronisation
Barrieren und Kritische Abschnitte werden von MPI nicht unterstützt. Dies ist auch nicht notwendig, da jeder Prozess nur Zugriff auf lokalen Speicher hat. Eine Koordinierung des Programmablaufs erfolgt ausschließlich durch das Versenden von Nachrichten mit blockierenden
Funktionen. MPI unterstützt bei vielen Kommunikationsfunktionen sowohl synchrone als auch
asynchrone Varianten.
8.5 Globale Operatoren
MPI unterstützt eine Vielzahl von Datenverteilungs- und Aggregierungsfunktionen. Scatter-,
Gather und Reduktionsoperationen sind als globale blockierende Operationen implementiert,
die von alle beteiligten Prozessen gleichzeitig ausgeführt werden müssen.
- 34 -
9 OpenMP
OpenMP57 ist eine Programmierschnittstelle (API) für Fortran und C/C++, um parallele Programme mit gemeinsamem Speicher zu realisieren. Es wurde mit der Maßgabe entworfen,
vorhandenen, sequentiellen Code auf einfache Weise zu parallelisieren und dabei schrittweise vorgehen zu können.
Ein Ziel von OpenMP ist, dass der vorhandene sequentielle Programmcode nicht oder nur minimal verändert und statt dessen durch Compilerdirektiven ergänzt wird. Ein OpenMP-Programm
kann daher auch von einem nicht-OpenMP-fähigen Compiler übersetzt werden58 , da unbekannte Direktiven grundsätzlich ignoriert werden. Die Aufgabe des Programmierers ist es daher
nicht, Algorithmen in eine parallele Version zu ändern, sondern parallelisierbare Abschnitte
zu identifizieren, zu kennzeichnen und zu spezifizieren. Dazu stellt OpenMP eine Reihe von
Direktiven, Bibliotheksfunktionen und Variablen bereit.
9.1 Verfügbarkeit
Um OpenMP nutzen zu können, ist ein entsprechender Compiler erforderlich. In Tabelle 2 im
Anhang A.2 ist eine Übersicht verschiedener Produkte aufgeführt, die OpenMP unterstützen.
9.2 Parallelisierungskonzept
Grundsätzlich arbeitet OpenMP nach dem Thread-Konzept. Im Gegensatz zu den meisten anderen Konzepten, bei denen ein Programm von Anfang parallel ausgeführt wird, benutzt OpenMP
das Fork-Join-Modell zur Parallelisierung, wie in Abbildung 11 dargestellt. Das bedeutet, dass
zunächst nur ein einzelner Thread aktiv ist. Erst, wenn ein parallelisierbarer Programmabschnitt
erreicht wird, erzeugt OpenMP neue Threads und beendet diese, sobald der Abschnitt wieder
verlassen wird.
Der notwendige Programmcode für die Erzeugung Synchronisierung der OpenMP-Threads
wird durch den Compiler erzeugt. Wie dies konkret vonstatten geht und ob Threads oder Prozesse zum Einsatz kommen, hängt von dessen Implementierung ab und ist aus Sicht des Programmierers irrelevant. Vom Programmierer ist lediglich eine Kennzeichnung und Spezifikation der
zu parallelisierenden Programmabschnitte notwendig. Hierfür stellt OpenMP eine Anzahl von
Compilerdirektiven, Routinen und Variablen für die Sprachen C/C++ und Fortran bereit.
9.3 Speichermodell
OpenMP implementiert ein Modell mit verteiltem Speicher, wobei dieser sowohl gemeinsam
(shared) als auch einzeln (private) genutzt werden kann. Der private Speicher eines Threads liegt
ausschließlich in seinem eigenen Adressraum und ist nur für ihn selbst sichtbar. Standardmäßig
57
Vgl. hierzu: [C HAPMAN 2008],[H OFFMANN 2008],[C HANDRA],[R AUBER 2008] S.127ff., [R AUBER 2007] S.
207ff., 354ff.
58
Mit Einschränkungen bei der Verwendung von OpenMP Bibliotheksfunktionen, die dann natürlich nicht Verfügbar sind, was beim Linken zu einem Fehler führt. Durch die Verwendung eines #ifdef _OPENMP-Blocks kann
dies verhindert werden.
- 35 -
Abbildung 11: OpenMP Fork-Join-Modell
Quelle: [C HAPMAN 2008] S. 24
sind alle Variablen als privat deklariert. Gemeinsame Variablen müssen in der #pragma omp
parallel Direktive angegeben werden.
9.4 Synchronisation
Für die Synchronisation stellt OpenMP je nach Anforderung verschiedene Mechanismen bereit.
Kritische Abschnitte Abschnitte innerhalb eines Parallelen Anweisungsblocks, die Anfällig
für eine Race-Condition sind, können durch Markierung mit #pragma omp critical gekennzeichnet werden. OpenMP stellt dadurch sicher, dass der Programmabschnitt nur von einem
Thread zur selben Zeit ausgeführt wird. Betrifft dies nur eine kleine Anweisung, beispielsweise eine Operation auf eine gemeinsame Variable wie a=a+b, kann diese als Atomare Operation durch #pragma omp atomic gekennzeichnet werden, die für OpenMP mit geringerem
Verwaltungsaufwand zur Synchronisation der beteiligten Prozesse verbunden ist. Durch die
Atomare Anweisung wird sichergestellt, dass zwischen den einzelnen dafür notwendigen
Prozessoranweisungen kein Prozesswechsel stattfindet.
Locks Der konkurrierende Zugriff auf Ressourcen kann durch Locking-Mechanismen gesteuert werden. Im Gegensatz zur Verwendung von critical, das automatisch Locks erzeugt, ist
es hier die Aufgabe des Programmierers, Locks zu erzeugen und wieder freizugeben. Dies
ist notwendig, wenn auf die zu schützende Ressource nicht nur in einem einzelnen Programmabschnitt zugegriffen wird und somit nicht durch einen critical-Block geschützt werden
kann.
OpenMP stellt keine Funktionen zur Behandlung von Deadlock-Situationen bereit.
Barrieren Barrieren lassen sich einfach durch Einfügen von #pragma omp barrier hinter einem Parallelen Abschnitt realisieren.
Geordnete Ausführung In OpenMP kann die Ausführungsreihenfolge paralleler Abschnitte durch den Programmierer gesteuert werden. Dies kann z.B. Notwendig sein, wenn Ausgaben realisiert werden müssen. Ein mit #pragma omp ordered gekennzeichneter Bereich
innerhalb eines Parallelen Abschnitts wird exakt so ausgeführt, als würde er seriell durchlaufen werden.
- 36 -
9.5 Globale Operatoren
OpenMP unterstützt keine expliziten Kommunikationsanweisungen. Globale Operatoren können daher nur indirekt durch entsprechende Spezifikation der parallelen Abschnitte genutzt werden. Hierfür wird das reduction - Konstrukt bereitgestellt:
reduction ( operator : Variable [,Variable] ...)
Wird beispielsweise eine Schleife, innerhalb der auf eine gemeinsame Variable schreibend zugegriffen wird, parallel ausgeführt, sind zeitaufwändige Synchronisierungsmaßnahmen erforderlich, um den Speicherinhalt für alle Threads konsistent zu halten. Dies muss jedoch nicht
immer notwendig sein. Durch das reduction-Konstrukt wird die Synchronisierung erst nach Beendigung aller Iterationen unter Verwendung des angegebenen Operators durchgeführt, zum
Beispiel für die Summenfunktion:
int sum(int * v, int size){
result=0;
#pragma omp parallel for reduction (+:result)
for(i=0, i<size, i++){ result+=v[i]; }
return result;
}
- 37 -
10 Andere
10.1 BOINC
BOINC - Berkeley Open Infrastructure for Network Computing59 ist eine an der Universität
Berkeley, Californien entwickelte Umgebung für das Grid-Computing. BOINC funktioniert
nach dem Client-Server-Prinzip. Ein BOINC-Client wird auf einer Anzahl von Rechnern installiert und meldet sich bei einem Server an, von dem er Rechenaufgaben zugeteilt bekommt
und die Resultate zurücksendet. Die BOINC-API ist sehr umfangreich und speziell für das Verteilte Rechnen mit sehr hoher Anzahl an Clients in heterogener Rechnerumgebung entwickelt.
Basierend auf BOINC wurde weltweit Rechenleistung für verschiedene wissenschaftliche Projekte wie z.B. Seti@Home gebündelt.
10.2 BLAS
BLAS ist eine Abkürzung für Basic Linear Algebra Subroutines und bezeichnet Bibliotheken,
die lineare algebraischen Funktionen bereitstellen. Es existiert kein formaler Standard, dennoch
hat sich ein konkreter Satz an Funktionen als de-facto Standard etabliert, der mit dem Begriff
BLAS bezeichnet wird. Je nach Umfang werden diese in Levels 1-3 eingeordnet, wobei Level 1 Vektor-Operationen, Level 2 Vektor-Matrix-Operationen und Level 3 Matrix-Operationen
unterstützt.
BLAS ist keine Parallele Programmierbibliothek, jedoch existieren auch Implementierungen,
die PVM, MPICH oder LAM nutzen. Eine Parallelisierung mittels BLAS gestaltet sich so, dass
das zu lösende Problem in eine Reihe von Matrizenoperationen überführt wird, die dann durch
BLAS-Funktionen ausgeführt werden.
Für BLAS gibt es eine Reihe verschiedener, zum Teil hoch optimierter Implementierungen, die
häufig von Hardware-Herstellern stammen und speziell an deren Produkte angepasst sind. Weiterhin sind z.B. auch parallel arbeitende Versionen für verschiedene MPI-Implementierungen
verfügbar.
Unix-Distributionen enthalten in der Regel mehrere BLAS-Implementierungen. Einige davon
sind Beispielsweise „refblas3”, „scalapack” und „lapack3”.
10.3 PThreads
PThreads ist eine Interface-Spezifikation zur Programmierung von Threads entsprechend dem
POSIX.1-Standard für Unix-Basierte Systeme. Aktuelle Implementierungen sind LinuxThreads60
und die Native POSIX Threads Library (NPTL).
10.4 Parallel Virtual Machine
PVM bildet einen virtuellen Parallelrechner mit nicht zwingend homogener Rechnerstruktur
und lokal zugeordnetem Speicher ab. Die Kommunikation der beteiligten Knoten erfolgt durch
59
60
Homepage: http://boinc.berkeley.edu
Wird ab glibc 2.4 nicht mehr unterstützt, stattdessen wird seit glibc 2.3.2 NPTL verwendet.
- 38 -
Message-Passing. PVM kann als Vorgänger von MPI betrachtet werden.
PVM wird durch einen Dienst (pvmd) realisiert, der auf den beteiligten Rechnern gestartet
wird und als Schnittstelle zu allen Rechnern fungiert. Die Parallelisierung erfolgt durch einen
Elternprozess, der den Dienst dazu veranlasst, weitere Instanzen des Programms auf den anderen Rechnern zu starten und die Kommunikation zwischen den Instanzen zu steuern. PVMProgramme müssen daher auf allen Rechnern in ausführbarer Form vorliegen und der Nutzer
muß Zugriffsrechte auf alle Rechner haben, da sowohl der Dienst als auch die Programme unter
seiner Nutzerkennung laufen.
- 39 -
11 Vergleichende Betrachtung
Um den Programmieraufwand sowie die Rechengeschwindigkeit der vorgestellten Bibliotheken
im Ansatz vergleichen zu können, wurde ein Algorithmus zur parallelen Matrizenmultiplikation in serieller Form implementiert und anschließend für die Verwendung mit den parallelen
Bibliotheken angepasst.
11.1 Implementierung
Im Folgenden wird die serielle Version des Algorithmus vorgestellt und auf die speziellen Merkmale der Versionen für jede der untersuchten parallelen Ansätze eingegangen. Der vollständige
Quellcode ist, sofern nicht aufgeführt, auf der beiliegenden CD zu finden. Da es um den Vergleich der Techniken geht, wurde auf Optimierungen seitens des Compilers verzichtet.
11.1.1 Matrizenmultiplikation Seriell
Die Serielle Version ist im Algorithmus 1 dargestellt. Das Programm besteht aus zwei Teilen,
einer Funktion zur Matrizenmultiplikation und dem Hauptprogramm, dass diese aufruft und die
Zeitmessung durchführt.
Die Compilierung erfolgt mittels:
$gcc -std=C99 standard.c -o standard
11.1.2 Matrizenmultiplikation UPC
Für UPC sind nur wenige Änderungen erforderlich. Vor allem muss in der Funktion mult_matrix()
die Hauptschleife durch das UPC-Pendant ersetzt werden.
[...]
upc_forall(int ay=0;ay<y1;ay++;ay){
[...]
Weiterhin ist die Ergebnismatrix R als shared zu deklarieren. Hierbei wird eine Blockgröße
entsprechend der Zeilengröße gewählt, so dass die Matrix zeilenweise auf die Threads verteilt
wird:
shared double * R=upc_all_alloc(dim,dim*sizeof(double));
Bei der Zeitmessung und der Ausgabe ist zu beachten, dass diese nur von einem einzigen Prozess durchgeführt wird, da es sonst zu mehrfachen Ausgaben kommt. Für die Compilierung
muss ein UPC-Compiler eingesetzt werden, in diesem Fall der GCC-UPC 4.0.3.5.
Die Compilierung erfolgt mittels:
$upc-gcc -std=C99 -lupc -static upc.upc -o upc
- 40 -
Algorithm 1 Matrizenmultiplikation Seriell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# include
# include
# include
# include
# include
# include
< s t d l i b . h>
< s t d i o . h>
<math . h>
< s t r i n g . h>
< s y s / t i m e . h>
< t i m e . h>
i n t m a t r i x _ m u l t ( i n t x1 , i n t y1 , d o u b l e ∗ A, i n t x2 , i n t y2 , d o u b l e ∗ B , d o u b l e ∗ R ) {
i f (A! =NULL && B! =NULL && R! =NULL) {
i n t o f s _ a =0 , o f s _ b =0 , o f s _ r = 0 ;
f o r ( i n t ay = 0 ; ay <y1 ; ay ++) {
f o r ( i n t bx = 0 ; bx <x2 ; bx ++) {
o f s _ r = ay ∗ x2 +bx ; R [ o f s _ r ] = 0 . 0 ;
f o r ( i n t ax = 0 ; ax <x1 ; ax ++) {
o f s _ a = ay ∗ x1 + ax ; o f s _ b = ax ∗ x2 +bx ;
R [ o f s _ r ]+=A[ o f s _ a ] ∗B [ o f s _ b ] ;
}
}
}
}
return 1;
}
i n t main ( i n t a r g c , char ∗ a r g v [ ] ) {
i f ( a r g c < 1 ) r e t u r n −1;
/ / m a t r i x e r z e u g e n und i n i t i a l i s i e r e n
i n t dim= a t o i ( a r g v [ 1 ] ) ;
d o u b l e ∗ A= ( d o u b l e ∗ ) m a l l o c ( dim∗dim∗ s i z e o f ( d o u b l e ) ) ;
d o u b l e ∗ B= ( d o u b l e ∗ ) m a l l o c ( dim∗dim∗ s i z e o f ( d o u b l e ) ) ;
d o u b l e ∗ R= ( d o u b l e ∗ ) m a l l o c ( dim∗dim∗ s i z e o f ( d o u b l e ) ) ;
f o r ( i n t i = 0 ; i <dim∗dim ; i ++) {
A[ i ] = 1 ; B [ i ] = 1 ; R [ i ] = 0 ;
}
s t r u c t t i m e v a l time1 , time2 ;
i n t ms1 , ms2 ;
/ / z e i t m e s s u n g und b e r e c h n u n g
g e t t i m e o f d a y (& t i m e 1 , NULL) ;
m a t r i x _ m u l t ( dim , dim , A, dim , dim , B , R ) ;
g e t t i m e o f d a y (& t i m e 2 , NULL) ;
ms1= t i m e 1 . t v _ s e c ∗1000000+ t i m e 1 . t v _ u s e c ;
ms2= t i m e 2 . t v _ s e c ∗1000000+ t i m e 2 . t v _ u s e c ;
i n t d e l t a =ms2−ms1 ;
double d e l t a _ s e c =( double ) d e l t a / 1 0 0 0 0 0 0 ;
p r i n t f ( "%i %f \ n " , dim , d e l t a _ s e c ) ;
return 0;
}
- 41 -
11.1.3 Matrizenmultiplikation MPI
Für MPI gibt es prinzipiell zwei Möglichkeiten der Implementierung, die jede ihre Vor- und
Nachteile hat: Implementation mit Globalen Operationen oder ein Master-Worker-Modell.
Die Verwendung globaler Operationen zur Verteilung der Daten erfordert weniger Aufwand
als das Master-Worker-Modell, ist jedoch weniger flexibel in der optimalen Ausnutzung der
Rechenkapazität.
Im Gegensatz zu UPC oder OpenMP kann sich bei MPI die Parallelisierung nicht nur auf die
Anpassung der Funktion matrix_mult beschränken. So muss beispielsweise beim Programmstart eine Initialisierung von MPI erfolgen. Daher wurde der Ansatz gewählt, die originale
Funktion matrix_mult zu verwenden, deren Aufruf jedoch mit einem MPI-Konstrukt zu umgeben, so dass jeder Prozess nur Teile der Matrizen berechnen muss.
MPI verteilt Daten stets in zusammenhängenden Blöcken. Für die Verteilung der Matrix A bedeutet dies, dass jeder Prozess mehrere aufeinanderfolgende Zeilen erhält. Da die Gesamtzahl
der Zeilen nicht notwendigerweise ein Vielfaches der Prozessanzahl darstellt, ist eine ungleiche
Verteilung notwendig.
Die Anzahl und Größe der Pakete, die jeder Prozess empfängt, muss allen Teilnehmern beim
Aufruf der Funktionen bekannt sein, d.h. der Empfänger muss wissen, wieviel Daten er bekommt, da er einen entsprechend dimensionierten Empfangspuffer bereitstellen muss.
Globale Operationen
Bei der Verwendung globaler Operationen erfolgt das Scheduling der Daten statisch. Dazu berechnet der Root-Prozess anhand der Prozessanzahl und der Datenmenge, wie die Aufteilung
erfolgt. Der Algorithmus 2 stellt einen Auszug aus dem entsprechenden Testprogramm dar.
Master-Worker-Modell
Die Implementierung des Master-Worker-Modells erfolgt in der Form, dass Worker-Prozesse
Nachrichten an den Master-Prozess schicken, um diesen zu veranlassen, entweder einen neuen Datenblock zu senden oder ein Ergebnis entgegenzunehmen. Das Master-Programm wird
beendet, sobald alle Datenblöcke versendet und alle Ergebnisse empfangen wurden. Da der Algorithmus deutlich umfangreicher ist als bei Verwendung globaler Operatoren wird auf eine
Darstellung verzichtet.
11.1.4 Matrizenmultiplikation OpenMP
Der Aufwand für OpenMP ist ähnlich dessen für UPC. In der Funktion mult_matrix() werden
Pragmas gesetzt, um dem Compiler mitzuteilen, dass die Indexvariablen ofs_a, ofs_b und ofs_r
privat und die Matrizen A,B und R verteilt sind. Weiterhin wird eine statische Aufteilung der
for-Schleife auf die einzelnen Threads festgelegt, wodurch jeder Thread die selbe Anzahl von
Schleifen durchläuft.
#pragma omp parallel for schedule(static) \
private(ofs_a,ofs_b,ofs_r) shared (A,B,R)
for(int ay=0;ay<y1;ay++){
...
}
- 42 -
Algorithm 2 Matrizenmultiplikation MPI Global
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# i n c l u d e <mpi . h>
// ...
i n t main ( i n t a r g c , char ∗ a r g v [ ] ) {
//
...
i n t rank , nproc ;
M P I _ I n i t (& a r g c ,& a r g v ) ;
MPI_Comm_rank (MPI_COMM_WORLD,& r a n k ) ;
MPI_Comm_size (MPI_COMM_WORLD,& n p r o c ) ;
i n t s s i z e s [ nproc ] , s o f s [ nproc ] ;
i f ( r a n k ==0) {
f o r ( i n t i = 0 ; i < n p r o c ; i ++) {
i n t rows =dim / n p r o c ; i f ( rows ==0) rows = 1 ;
s s i z e s [ i ] = dim∗ rows ; s o f s [ i ] = dim∗ rows ∗ i ;
i f ( i == n p r o c −1){ s s i z e s [ i ] + = ( dim%rows ) ∗dim ; }
}
}
MPI_Bcast (&dim , 1 , MPI_INT , 0 ,MPI_COMM_WORLD) ;
MPI_Bcast ( B , dim∗dim , MPI_DOUBLE , 0 ,MPI_COMM_WORLD) ;
MPI_Bcast ( s s i z e s , n p r o c , MPI_INT , 0 ,MPI_COMM_WORLD) ;
M P I _ S c a t t e r v (A, s s i z e s , s o f s , MPI_DOUBLE , A, s s i z e s [ r a n k ] , MPI_DOUBLE , 0 ,MPI_COMM_WORLD) ;
m a t r i x _ m u l t ( dim , s s i z e s [ r a n k ] / dim , A, dim , dim , B , R ) ;
MPI_Gatherv (A, s s i z e s [ r a n k ] , MPI_DOUBLE , A, s s i z e s , s o f s , MPI_DOUBLE , 0 ,MPI_COMM_WORLD
return 0;
}
Das Hauptprogramm wird lediglich zu Beginn um Anweisungen zur Festlegung der Anzahl zu
verwendender Prozesse ergänzt:
int procs=atoi(argv[1]);
omp_set_dynamic(0);
omp_set_num_threads(procs);
Die Compilierung erfolgt mittels des GCC-4.2:
$gcc-4.2 -fopenmp -std=C99 openmp.c -o openmp
11.2 Performance
Um den Einfluss des Betriebssystems beim Programmstart sowie eventuelle Initialisierungsphasen der Bibliotheken zu minimieren, wurde nur die Berechnungszeit für die eigentliche
Multiplikation ermittelt. Die Berechnungszeit wurde in Abhängigkeit von Matrizengröße und
Prozessanzahl gemessen. Jede Messung wurde drei mal Durchgeführt und das beste Resultat
ausgewertet.
Die Tests wurden auf einem Parallelrechner mit 8 Dual Core AMD Opteron(tm) Prozessoren
mit 1 GHz Taktfrequenz und 32GB RAM durchgeführt. Als Betriebssystem diente Ubuntu 2.6.
Außer für UPC wurde als Compiler der GCC 4.1.2 verwendet. Alle Messergebnisse sind in
Tabellenform im Anhang B.2 aufgeführt.
- 43 -
Zunächst ist für UPC ein überraschendes Resultat festzustellen. Mit nur einem Prozessor ist die
UPC-Version um etwa eine Potenz langsamer als die Serielle Variante. Dies deutet auf einen
enormen Overhead hin, der durch UPC zur Speicherverwaltung erzeugt wird. Parität mit der
Seriellen Version wird erst bei Verwendung von 8 Prozessoren erreicht.
Abbildung 12: Performance UPC
Quelle: Eigene Darstellung.
Bei OpenMP ist festzustellen, dass das parallelisierte Programm auf nur einem Prozessor deutlich langsamer ausgeführt wird, als die serielle Variante. Dies stellt insofern keinen Nachteil
dar, als das ein OpenMP-Programm auch ohne OpenMP-Unterstützung compiliert werden kann
und dann rein seriell ausgeführt wird. Auf diese Weise können ohne programmiertechnischen
Mehraufwand eine parallele und eine serielle Version des selben Programms erstellt werden.
Mit steigender Prozessorzahl ist eine entsprechend gute Skalierung zu erkennen.
Der Vergleich einer Master-Worker-Implementierung und einer Implementierung unter Verwendung globaler Operatoren unter MPI zeigt, dass bei wenigen Prozessoren die globalen Operatoren effizienter arbeiten und teilweise um den Faktor 2 schneller sind. Mit steigender Prozessorzahl ist jedoch das Worker-Slave-Modell leicht überlegen, da hier Differenzen in der Auslastung
einzelner Prozessoren weniger starke Auswirkungen zeigen. Während bei globalen Operatoren
die zu Verarbeitende Datenmenge pro Prozess fest vorgegeben ist und daher die Dauer der
Gesamtoperation von dem langsamsten Prozess abhängt, erfolgt beim Master-Worker-Konzept
eine lastabhängige Verteilung der Datenpakete.
Ein Vergleich zwischen OpenMP und MPI zeigt, dass MPI schneller arbeitet. Hierbei wurde
jedoch nur die jeweils einfachste Implementierung gewählt, so dass sich noch keine allgemeingültige Aussage über die Geschwindigkeiten der Implementierungen machen lassen. Sowohl
OpenMP als auch MPI erlauben weitere Einstellungen, um die Parallelisierung effizienter zu
gestalten. Abbildung 17 zeigt das Verhältnis der Rechenzeiten vom OpenMP und MPI. Für sehr
kleine Problemgrößen und hoher Prozessoranzahl arbeitet MPI deutlich langsamer als OpenMP.
Dieses Verhältnis kehrt sich bei steigender Problemgröße schnell um.
- 44 -
Abbildung 13: Performance OpenMP
Quelle: Eigene Darstellung.
Abbildung 14: Performance MPI
(a) Master-Worker
(b) Globale Operatoren
Quelle: Eigene Darstellung.
11.3 Bewertung
11.3.1 UPC
Der verwendete Compiler upc-gcc erzeugt Programme, die von Anfang an parallel laufen. Es
ist nicht möglich, die parallele Ausführung gezielt auf bestimmte Programmabschnitte, z.B.
einzelne Funktionen, zu beschränken. Daher ist das zu parallelisierende Programm sehr stark an
die Anforderungen von UPC anzupassen. Die Parallelisierung eines existierenden Programms
ist nur mit erheblichem Aufwand möglich. Nur sehr wenige Compiler (z.B. der Berkeley UPC)
unterstützen die Verwendung von UPC-Code durch C++.
Die Unterstützung der UPC-Spezifikation durch einzelne Compiler ist sehr unterschiedlich. So
wurde beim Testen des UPC-GCC 4.0.3.5 mittels einfacher Beispielprogramme aus der UPC1.2 Dokumentation festgestellt, dass dieser anscheinend nicht Spezifikationsgerecht funktioniert, da sich diese nicht übersetzen lassen. So ist die schon Definition eines simplen statischen
- 45 -
Abbildung 15: Performance MPI vs. MPI Global
Quelle: Eigene Darstellung.
verteilten Arrays analog zu den Beispielen aus der Dokumentation durch beispielsweise shared
[2] int[10] nicht möglich und führt zu einem Syntaxfehler. Mit shared [2] int[] lässt sich sogar
ein Speicherzugriffsfehler während der Übersetzung provozieren. Die dynamische Erzeugung
von verteiltem Speicher mittels upc_alloc-Varianten hingegen scheint zu funktionieren, wenn
auch nicht Spezifikationsgerecht. So ist eine Festlegung der Blockgröße nicht möglich gewesen, so dass Speicherbereiche stets Byteweise über alle Threads verteilt wurden, was deutlich
zu Lasten der Geschwindigkeit geht.
Aufgrund der genannten Probleme wurde nach dem Release die neueste Version UPC-4.2.3.3
getestet, die jedoch das selbe Resultat lieferte.
UPC ist ein Projekt, dass sich noch in der Entwicklung befindet. Das Programmierkonzept ist
mit Ausnahme der Beschränkung auf reines C vom Funktionsumfang mit anderen Ansätzen zu
vergleichen, lediglich die Umsetzung durch die getesteten Compiler scheint weder ausgereift zu
sein, noch entspricht sie der in der Spezifikation dargelegten Funktionsweise. Daher wird UPC
als nicht für den Produktiven Einsatz geeignet eingestuft.
11.3.2 MPI
Ein MPI-Programm ist schwieriger zu implementieren als Beispielsweise mit OpenMP. Dafür bietet MPI mehr Freiheiten bezüglich der Strukturierung des Programms. Bei Verwendung
globaler Operatoren ist der Aufwand vergleichbar mit dem für OpenMP. Eine flexible Aufgabenverteilung nach dem Master-Worker-Prinzip ist deutlich komplizierter zu implementieren,
bietet dafür aber eine höhere Geschwindigkeit. In beiden Fällen ist MPI spürbar schneller als
OpenMP. Ein weiterer Vorteil von MPI ist, dass sich komplexe rechnerübergreifende Kommunikationshierarchien implementieren lassen, wodurch es sich besonders für hochgradig parallele
Anwendungen eignet.
- 46 -
Abbildung 16: Performance OpenMP vs. MPI Global
Quelle: Eigene Darstellung.
Abbildung 17: Performance OpenMP vs. MPI Global
Quelle: Eigene Darstellung.
- 47 -
11.3.3 OpenMP
Eine Parallelisierung sowohl vorhandenen Codes als auch als Neuentwicklung stellt sich mit
OpenMP außerordentlich einfach dar. Das Konzept der Schrittweisen Parallelisierung lässt sich
gut umsetzen und es werden schnell erste Resultate erzielt. Dennoch muss der korrekten Spezifikation der parallelen Abschnitte und insbesondere der Unterscheidung zwischen privaten und
nicht-privaten Variablen viel Aufmerksamkeit gewidmet werden.
Im Gegensatz zu MPI kann ein OpenMP-Programm nur auf einem einzigen Rechner ausgeführt
werden. Die maximale sinnvolle Parallelisierbarkeit ist dadurch entsprechend der Anzahl vorhandener Prozessoren eingeschränkt. Der Geschwindigkeitsgewinn durch Parallelisierung ist
deutlich messbar, wenn auch nicht so groß wie bei MPI.
- 48 -
Teil III
Parallelisierung im Data Mining
Da sich diese Arbeit mit der Anwendung des verteilten Rechnens speziell im Data Mining befasst, gibt dieser Teil eine Einführung in das Data-Mining und die in dieser Arbeit verwendeten
Verfahren. Es werden mögliche Umsetzungen für ein verteiltes Rechen im Data-Mining und exemplarisch die konkrete Implementierung zweier Ansätzes vorgestellt. Dabei wird gezeigt, dass
eine Parallelisierung nicht in jedem Fall eine große Hürde darstellt, die komplizierte Programme erfordert, sondern dass Bereits mit einfachen Mitteln ein signifikanter Geschwindigkeitszuwachs erzielt werden kann.
This chapter deals with the parallization of Data Mining algorithms. It provides a brief introduction into Data Mining and the methods to be parallelized. This chapter also shows that
parallization does not necessarily mean complicated algorithms, but that the performance of
Data Mining algorithms can be significantly improved using standard tools.
- 49 -
12 Data Mining Grundlagen
Data Mining ist Bestandteil einer Disziplin, die als Wissensextraktion aus Datenbanken - Knowledge Discovery in Databases (KDD) - bekannt ist. Es befasst sich mit der Gewinnung von neuem Wissen aus vorhandenen Daten. Dabei werden die Informationen, die in den Daten implizit
enthalten sind, durch verschiedene Data-Mining-Verfahren extrahiert und in geeigneter Form
dargestellt. Das auf diese Weise explizierte Wissen kann beispielsweise durch Modelle, Regeln
oder Diagramme ausgedrückt und somit nutzbar gemacht werden.61
12.1 Ablaufmodell
Als „Data Mining” bezeichnet man sowohl die Anwendung der Methoden als auch das Forschungsgebiet selbst. Da es ein relativ neues und stark experimentelles Forschungsgebiet ist,
sind sowohl die Methoden als auch deren Anwendung Forschungsgegenstand. Besonders letzteres ist für den erfolgreichen Einsatz des Data Mining von Bedeutung, da hier stets auf die konkrete Zielstellung und die verfügbaren Daten eingegangen werden muss. Daher existiert kein für
alle Fälle geeignetes Verfahren. Es lässt sich lediglich ein allgemeines Vorgehensmodell ableiten, das den Data-Mining-Vorgang als iteratives Phasenmodell beschreibt. Jedoch berücksichtigt dies weder konkrete Methoden noch lassen sich die einzelnen Phasen exakt gegeneinander
abgrenzen, da sie sich in der Praxis teilweise überschneiden und stark aufeinander aufbauen. Es
lassen sich folgende Phasen definieren:
• Datenvorverarbeitung (Aufbereitung, Kodierung)
• Analyse
• Datennachbearbeitung (Auswertung, Visualisierung)
• Interpretation
Vor der eigentlichen Analyse der Daten ist eine Datenvorverarbeitung notwendig. Neben der
inhaltlichen Bereinigung (Eliminieren von Fehlern, fehlenden Werten oder Ausreißern; Auswählen von Untermengen, Test- und Trainingsdaten) müssen die Daten in eine Form gebracht
werden, die durch die gewählte Data-Mining-Methode verarbeitet werden kann, zum Beispiel
durch Normalisierung, Skalierung, Intervallbildung oder Transformation.
Die Datenerhebung als Teil der Datenvorverarbeitung hat dafür Sorge zu tragen, dass die einen
Sachverhalt beschreibenden Informationen auch in der Datenmenge enthalten sind und andererseits irrelevante, aber signifikante Informationen die Datenmenge nicht verfälschen. Ebenso
können ungenaue, fehlende oder falsche Werte das Ergebnis beeinträchtigen oder unbrauchbar
machen. Die Datenvorverarbeitung hat entscheidenden Einfluss auf das Ergebnis der eingesetzten Methoden, da durch die Wahl ungeeigneter Vorverarbeitungsverfahren die Daten verfälscht
werden können. Es ist notwendig, bei der Kodierung die Daten korrekt und reproduzierbar abzubilden und dabei deren implizite Eigenschaften und Beziehungen zu berücksichtigen.
Die Datenanalyse bezieht sich auf die eigentliche Anwendung der Data-Mining-Methoden. Je
nach Zielstellung finden Methoden aus den Bereichen Klassifikation, Clustering, Prognose oder
Assoziation Anwendung. Um die für die zu analysierenden Daten optimale Methode aus dem
61
Dieses Kapitel wurde in ähnlicher Form vom Autor in [W ISSUWA 2003] und [C LEVE 2005] aufgeführt.
- 50 -
jeweiligen Bereich zu ermitteln, sind umfangreiche Experimente mit verschiedenen Methoden
und Parametern notwendig.
Die Validierung dient der Überprüfung des Data-Mining-Ergebnis auf tatsächliche Korrektheit.
Dies geschieht meist durch Anwenden des erstellten Modells auf ausgewählte Testdaten.
Datenvorverarbeitung (inkl. Datenerhebung) sowie die Validierung sind dem Data-Mining-Prozess
vor- bzw. nachgelagert. Beide sind stark anwendungsbezogen und daher nur bedingt dem Data
Mining selbst zuzuordnen. Jedoch haben sie großen Einfluss auf den Erfolg des Data Mining.
Ein ähnliches Modell wurde durch das CRISP-DM Konsortium erstellt, das mit dem Ziel entwickelt wurde, ein generelles Vorgehensmodell für den praktischen Einsatz von Data Mining in
Unternehmen zu bieten.
Abbildung 18: CRISP-DM Phasenmodell
Quelle: http://www.crisp-dm.org
12.2 Klassifikation der Verfahren
In der Literatur ist eine Vielzahl von Data-Mining-Algorithmen beschrieben, von denen viele
in der Praxis mit Erfolg eingesetzt werden.62 Die Anwendungsmöglichkeiten des Data Mining
sind außerordentlich vielfältig. Es lassen sich drei grundlegende Formen der Anwendung unterscheiden:
1. Klassifikation,
2. Assoziation,
3. Clustering,
4. Vorhersage.
Bei der Klassifikation werden überwachte Lernverfahren eingesetzt. Es wird anhand der Attribute von bereits klassifizierten Objekten ein Klassifikator erzeugt, der in der Lage ist, unbekannte Objekte ebenfalls korrekt zu klassifizieren. Als Klassifikatoren können zum Beispiel
62
Vgl.: [W ITTEN 2001],[W ITTEN 2005],[L ÄMMEL 2004],[L ÄMMEL 2003],[A LPAR 2000],[NAKHAEIZADEH 1998]
- 51 -
Entscheidungsbäume, k-Nearest-Neighbour oder Neuronale Netze zum Einsatz kommen. Typische Anwendungen sind z. B. Klassifikation von Kunden oder die Schrifterkennung.
Die Assoziation unterscheidet sich von der Klassifikation dadurch, dass nicht nur die Klasse,
sondern die Ausprägungen beliebiger Attribute und Attributkombinationen eines Objektes prognostiziert werden können. Als Methoden kommen hier der a-priori-Algorithmus oder wiederum Neuronale Netze in Betracht. Beispielanwendungen findet man in der Warenkorbanalyse
oder beim Wiederherstellen eines verrauschten oder fehlerhaften Pixelmusters anhand eines
vorher trainierten Beispiels.
Das Clustering, ein unüberwachtes Verfahren, wird zur Bildung von Gruppen (Cluster) einander
ähnlicher Objekte aus einer Grundmenge eingesetzt. Dabei kommen verschiedene multivariate Verfahren zum Einsatz. Es kann verwendet werden, um die Attribute herauszufinden, die
wesentliche Merkmale einer Gruppe darstellen oder durch die sie sich von anderen Gruppen
unterscheiden. Bekannte Clustering-Verfahren sind das K-Means-Verfahren und die von Teuvo Kohonen entwickelten Selbstorganisierenden Karten. Die mathematisch motivierten Support
Vector Machines stellen ebenfalls einen erfolgversprechenden Clustering-Ansatz dar.
Die Vorhersage ähnelt der Klassifikation. Sie dient der Bestimmung von Zielgrößen anhand
gegebener Attributausprägungen. Im Gegensatz zur Klassifikation liefert die Vorhersage jedoch
quantitative Werte.
- 52 -
12.3 Künstliche Neuronale Netze
Als Ansatz zur Parallelisierung im Data Mining sollen zwei Vertreter der Künstlichen Neuronalen Netze dienen: Feed-Forward-Netze und Selbstorganisierende Karten. Dieses Kapitel gibt
eine grobe Einführung in die zugrundeliegenden Konzepte beider Verfahren. Für eine Detaillierte Darstellung sei auf die Literatur verwiesen.63
Künstliche neuronale Netze sind eine stark idealisierte Nachbildung der Funktionsweise von
biologischen Neuronalen Netzen, wie beispielsweise Gehirne oder Nervensysteme. Die Verarbeitung von Informationen erfolgt nicht durch komplexe Algorithmen, sondern vielmehr durch
sehr einfache Einheiten, die allerdings in großer Zahl vorhanden sind und untereinander Informationen austauschen.
Analog zu ihren biologischen Vorbildern bestehen künstliche neuronale Netze aus Neuronen
und einem Verbindungsnetzwerk. Die Informationsverarbeitung erfolgt mit Hilfe der Propagierungsfunktion, die für jedes Neuron den Aktivierungszustand anhand der von anderen Neuronen eingehenden Signale und der Verbindungsgewichte errechnet. Ein daraus abgeleitetes
Ausgabesignal wird dann an andere Neuronen über das Verbindungsnetzwerk weitergeleitet.
Ein künstliches neuronales Netz kann als gerichteter Graph mit gewichteten Kanten angesehen
werden, durch den definiert wird, welche Neuronen miteinander kommunizieren. Die Gewichte
dienen der Hemmung oder Verstärkung von Signalen. Zusammen mit dem Schwellwert (Bias)
der Neuronen, der bestimmt, ab welchem Aktivierungsgrad ein Neuron aktiv wird und Signale
aussendet, sind es die Gewichte, in denen das “Wissen” des Netzes gespeichert wird. Durch diese verteilte Repräsentation des Wissen sind neuronale Netze relativ unempfindlich gegenüber
unvollständigen und verrauschten Eingabemustern.
Die Anordnung der Neuronen erfolgt typischerweise in Schichten von Neuronen gleichen Typs.
Die zu verarbeitenden Daten werden über Neuronen der Eingabeschicht an das Netz transferiert, indem diesen eine initiale Aktivierung entsprechend dem jeweiligen Trainingsmuster
zugewiesen wird. Diese Aktivierungen werden entsprechend der Propagierungsfunktion durch
das Netz verarbeitet. Nach vollständiger Propagierung kann die sogenannte „Netzantwort” als
Aktivierungswerte der Neuronen der Ausgabeschicht abgelesen und interpretiert werden. Zwischen Eingabe- und Ausgabeschicht können sich weitere Schichten befinden, die als Versteckte
Schichten bezeichnet werden.
Der Einsatz Neuronaler Netze sind immer dann sinnvoll, wenn sich andere Lösungsansätze wie
zum Beispiel algorithmische Lösungen oder eine regelbasierte Wissensdarstelllung als nicht geeignet herausgestellt haben. Natürlich funktionieren Künstliche Neuronale Netze ebenfalls nach
einem Algorithmus, doch dient dieser nicht der Lösung eines Problems, sondern der Anpassung
des Netzes an das Problem.
12.3.1 Feed-Forward-Netze
Feed-Forward-Netze gehören zu den Klassifikatoren. Sie sind sind dadurch gekennzeichnet,
dass die Propagierung nur in eine Richtung von der Eingabeschicht hin zur Ausgabeschicht
erfolgt. Es existiert keine Rückkopplung, das Netz ist ein zyklenfreier Graph.64
63
64
Siehe dazu: [Z ELL 2000],[L ÄMMEL 2004]
Vgl.: [Z ELL 2000] S.78
- 53 -
Bei Feed-Forward-Netzen werden überwachte Lernverfahren eingesetzt. Dabei wird dem Netz
ein Eingabemuster präsentiert, das nach der Propagierung ein Ausgabemuster erzeugt. Durch
den Vergleich der Ausgabe mit der gewünschten Ausgabe (teaching input) kann für jedes Ausgabeneuron ein Fehlerwert entsprechend der Abweichung zur gewünschten Ausgabe errechnet
werden. Basierend auf diesem Fehlersignal, den Verbindungsgewichten und der Aktivierung
der Neuronen kann berechnet werden, wie die Verbindungsgewichte im Netz verändert werden
müssen, damit der Fehler minimiert wird. Dieser Vorgang wird iterativ für alle Trainingsmuster
durchlaufen bis der Fehler hinreichend klein ist.
Ein trainiertes Feed-Forward-Netz ist in der Lage, neue Eingabemuster entsprechend ihrer Ähnlichkeit zu gelernten Mustern zu klassifizieren.
12.3.2 Selbstorganisierende Karten
Unüberwachte Lernverfahren finden überall dort Anwendung, wo Datenmengen nach unbekannten Regeln zu klassifizieren sind, was beim Data Mining meist der Fall ist. Ein gutes Beispiel für die Anwendung unüberwachten Lernens sind die von Teuvo Kohonen entwickelten
Selbstorganisierenden Karten, auch Feature Maps genannt.65
Eine SOM ist ein Neuronales Netz dessen Neuronen typischerweise als zweidimensionale Gitterstruktur angeordnet sind. Die Anzahl der Neuronen beeinflusst die Genauigkeit und die Fähigkeit zur Generalisierung der SOM. Jedes Neuron besitzt einen gewichteten Vektor W als
Verbindung mit den Neuronen der Eingabeschicht, deren Anzahl n der Variablen im Eingaberaum entspricht:
Wi = [wi,1 , ..., wi,n ]
Das Trainieren der SOM beginnt mit der Initialisierung der Gewichtsvektoren durch Zufallszahlen im Intervall [-1,1]. Während des Trainings wird ein zufällig gewählter Eingabevektor I mit
dem Gewichtsvektor W jedes Neurons der Kartenschicht verglichen. Das Neuron, dessen Gewichtsvektor der Eingabe am ähnlichsten ist, wird das Gewinnerneuron (BMU, Best Matching
Unit). Als Abstandsmaß wird in der Regel die Euklidische Distanz verwendet.
v
u n
uX
d(I, W ) = t (Ii − Wi )2
i=0
Der Gewichtsvektor W des Gewinnerneurons wird anschließend so verändert, dass er dem Eingabevektor I ähnlicher wird. Das gleiche gilt für die Neuronen innerhalb eines Radius um das
Gewinnerneuron. Der Grad der Beeinflussung wird durch den Lernfaktor η und die Distanzfunktion h (meist Gauss-Funktion) bestimmt, die normalerweise nach jedem Trainingsschritt
reduziert werden:
Wj (t + 1) = Wj (t) + η(t)hcj (t)[X(t) − Wj (t)]
65
Vgl.:[KOHONEN 2001], [Z ELL 2000] S179ff.
- 54 -
Eine SOM hat die Eigenschaft einer topologieerhaltende Transformation eines hochdimensionalen Eingaberaumes auf eine niedrigere Dimension. Ähnliche Eingabevektoren werden dabei
auf benachbarte Neuronen projeziert. Da meist zweidimensionale SOMs zum Einsatz kommen,
ist eine Visualisierung der Ergebnisse relativ einfach.
- 55 -
13 Parallele Algorithmen
Die augenscheinlichste Möglichkeit zur Parallelisierung im Data-Mining stellt der verwendete
Algorithmus selbst dar. Abhängig davon, wie der Zugriff auf die Daten erfolgt, kann dies sogar
sehr einfach geschehen, wie im Folgenden anhand künstlicher Neuronaler Netze skizziert wird.
13.1 Serielle Selbstorganisierende Karte
Als Ausgangspunkt soll eine Kohonen-Karte dienen, die auf herkömmliche, serielle Weise implementiert ist. Ausgehend davon werden parallele Varianten implementiert, um verschiedene
Ansätze miteinander vergleichen zu können.
Das Netz wird in Form von Schichten von Neuronen abgebildet. Eine Schicht repräsentiert dabei
eine Anzahl von Neuronen gleichen Typs. Schichten können miteinander vernetzt werden. Dies
entspricht der vollständigen Vernetzung der Neuronen untereinander.
Die Struktur einer Schicht kann wie folgt implementiert werden:
typedef struct {
char type;
int size;
int wsize;
void * source;
double * act;
double * bias;
double * error;
double * weights;
} t_layer;
//
//
//
//
//
//
//
//
Neuronen-Typ
Anzahl Neuronen
Anzahl Gewichte pro Neuron
Vorhergehende Schicht
Aktivierungs-Vektor
BiasVektor
Fehler-Vektor
Gewichts-Matrix
Die Gewichtsmatrix W einer Schicht, wobei i die Anzahl der Quell-Neuronen und j die Anzahl
der Ziel-Neuronen ist, stellt sich wie folgt dar:

w0;0 w1;0
 w0;1 ...
Wij = 
 ...
...
w0;j ...

... wi;0
... ... 

... ... 
... wij
Neuronen als Arrays ihrer Eigenschaftswerte zu speichern, anstelle eine eigene NeuronenStruktur zu verwenden, hat den Vorteil, dass die Propagierungsfunktionen als Matrix-Operationen dargestellt werden können, die direkt auf den entsprechenden Schichten operieren. Das
ist zum einen sehr schnell, zum anderen lassen sich Matrix-Operationen sehr leicht parallelisieren.
Eine Propagierungsfunktion, die Aktivierungen einer Schicht mit den entsprechenden Gewichten auf die Aktivierung der nachfolgenden Schicht abbildet, hat somit die Form:
Ai+1 = Ai ∗ Wi
Analog dazu lässt sich auch die Rückwärtspropagierung des Fehlersignals z.B. beim Backpropagation-Lernverfahren als Matrizenoperationen darstellen:
- 56 -
Ei = Ei+1 ∗ Wi ∗ Oi
Algorithmus 3 zeigt eine Implementierung für das Trainingsverfahren einer Kohonen-Karte. Zur
Bestimmung des Gewinnerneurons wurde nicht der Euklidische Abstand, sondern das maximale Skalarprodukt verwendet, weshalb Eingabe- und Gewichtsvektoren in normalisierter Form
vorliegen müssen.
Algorithm 3 SOM Seriell
1 do{
2
for(int i=0;i<nPatterns;i++){
3
4
// propagation
5
for(int j=0;j<I->size;j++) { I->act[j]=pattern[i][j]; }
6
matrix_mult(O->wsize,O->size,O->weights, 1,I->size,I->act, O->act);
7
8
// find winner
9
id_max=0;
10
for(int j=0;j<nOutput;j++){if(O->act[j] > O->act[id_max]) id_max=j;}
11
12
maxx=id_max % sx;
13
maxy=id_max / sx;
14
15
// adjust weights
16
for(int j=0;j<nOutput;j++){
17
dx=maxx - (j%sx); dy=maxy - (j/sx);
18
19
rate=sqrt( (double)(dx*dx + dy*dy));
20
rate=factor * exp (- ((rate*rate) / (radius*radius)) );
21
len=0.0;
22
for(int k=0;k<nInput;k++){
23
O->weights[j*nInput+k]+=rate*(I->act[k]-O->weights[j*nInput+k]);
24
len+=O->weights[j*nInput+k]*O->weights[j*nInput+k];
25
}
26
27
len=1.0/sqrt(len);
28
for(int k=0;k<nInput;k++){
29
O->weights[j*nInput+k]*=len;
30
}
31
}
32
radius*=radius_factor;
33
}
34 }while(++cycle < cycles);
13.2 Parallele Selbstorganisierende Karte
Die Berechnungszeit für einen Trainingszyklus einer selbstorganisierenden Karte hängt hauptsächlich von der Anzahl der Trainingsmuster sowie der Größe der Kartenschicht ab. Ein Trainingsschritt läuft dabei in 3 Stufen mit unterschiedlichem Rechenaufwand ab:
1. Propagierung des Eingabemusters: O(n2 )
- 57 -
2. Bestimmung des Gewinnerneurons: O(n)
3. Anpassung der Gewichte: O(n2 )
Die einzelnen Stufen sind nicht unabhängig voneinander, so dass eine Paralleliserung nicht
durch Aufteilung der Trainingsmuster erfolgen kann. Stattdessen kann versucht werden, den
Trainingsschritt zu parallelisieren. Die besten Kandidaten stellen die Stufen 1 und 3 dar. Im
Folgenden wird die Parallelisierung unter Verwendung von OpenMP dargestellt. Der Bezug
zum jeweils Abgebildeten Quelltext findet durch Angabe der Zeilennummer in Klammern statt.
Ausgehend von Algorithmus 3 kann die Parallelisierung der einzelnen Stufen durch OpenMP
mit wenig Aufwand durch das Hinzufügen von Compilerdirektiven erfolgen. Die parallelisierte
Variante ist in Algorithmus 4 dargestellt.
Da sich innerhalb der äußersten Schleife parallelisierbare (z.B. Schleifen) und nicht-parallelisierbare Abschnitte (z.B. Anpassung des Trainingsradius) abwechseln, ist es wenig sinnvoll,
die Abschnitte einzeln zu parallelisieren, da so der zusätzliche Aufwand für die Erzeugung einzelner Prozesse den Geschwindigkeitsgewinn durch die parallele Berechnung schnell zunichte
machen würde. Die Aufteilung in einzelne Prozesse sollte stets so weit außen wie möglich
erfolgen (1).
Die Initialisierung der Eingabeschicht muss für alle Prozesse zum selben Zeitpunkt stattfinden,
da ansonsten die anschließende Propagierung unterschiedliche Resultate liefern würde. Dies
wird durch den Prozess vorgenommen, der die Schleife zur Initialisierung als erstes Erreicht.
Gleichzeitig wird implizit eine Barriere errichtet (7).
Die Propagierung mittels Matrizenmultiplikation kann durch Verwendung des parallelen Multiplikationsalgorithmus wie in Abschnitt 11.1.4 dargestellt erfolgen (10). Dieser muss jedoch
leicht modifiziert werden, da zum Zeitpunkt des Funktionsaufrufs das Programm bereits parallel ausgeführt wird. Somit ist dort die Zeile
#pragma omp parallel for schedule(static) ...
durch
#pragma omp for schedule(static) ...
zu ersetzen.
Nach der Bestimmung des Gewinnerneurons durch genau einen Prozess (12) erfolgt die Anpassung der Gewichte. Dies kann parallel für jedes Neuron der Kartenschicht erfolgen. Die jeweiligen Abstände zum Gewinnerneuron, der Lernradius sowie die Länge des Gewichtsvektors für
die Normalisierung müssen als privat deklariert werden (18).
Abschließend wird nach jeder Iteration über die Trainingsmuster der Lernradius angepasst, was
wiederum nur einmal pro Iteration geschehen darf und daher nur von einem Prozess durchgeführt wird (24).
- 58 -
Algorithm 4 Parallele SOM mit OpenMP
1 #pragma omp parallel
2 {
3
do{
4
for(int i=0;i<nPatterns;i++)
5
{
6
// propagation
7
#pragma omp single
8
for(int j=0;j<I->size;j++){ I->act[j]=pattern[i][j]; }
9
10
matrix_mult(O->wsize,O->size,O->weights, 1,I->size,I->act, O->act);
11
12
#pragma omp single
13
{
14
// find winner
15
...
16
}
17
18
#pragma omp for schedule(static) private(dx,dy,len,rate)
19
// adjust weights
20
for(int j=0;j<O->size;j++){
21
...
22
}
23
}
24
#pragma omp single
25
radius*=radius_factor;
26
}
27 } while(++cycle < cycles);
13.3 Performance Test
In Abbildung 19 ist dargestellt, wie sich für verschiedene Problemgrößen die Steigerung der
Parallelität auf die Rechenzeit auswirkt. Es ist sehr gut zu erkennen, dass eine zu starke, nicht
an die Problemgröße angepasste Parallelisierung zu steigenden Rechenzeiten führt.
Für größere Kohonen-Karten ist die Rechenzeit und der Speedup in Abhängigkeit von der Kartengröße in Abbildung 20 dargestellt. Es ist zu sehen, dass für p=12 der Speedup im Vergleich
zu p=4 im Verhältnis nur gering ist.
- 59 -
Abbildung 19: Rechenzeit SOM OpenMP für kleine Karten
Quelle: Eigene Darstellung.
Abbildung 20: SOM OpenMP Rechenzeiten und Speedup nach Kartengröße
Quelle: Eigene Darstellung.
- 60 -
14 Parallele Modelle
Verschiedene Gründe können gegen den Einsatz eines parallelen Algorithmus sprechen. Dies
ist beispielsweise der Fall, wenn keine passende parallele Implementierung existiert und auch
keine Anpassung des Quellcodes möglich ist, weil dieser zu komplex, nicht verfügbar oder der
Einsatz ein einmaliges Projekt ist, dass den Aufwand nicht rechtfertigt.
Wenn es nicht möglich ist, den Algorithmus zur Erzeugung eines Modells parallel auszuführen,
kann stattdessen versucht werden, das Modell selbst zu Parallelisieren. Man kann ein Modell
als Klassifikator betrachtet, der eine nichtsymmetrische Abbildung des Merkmalsraumes Rd auf
Klassen realisiert:
(
y
f : Rd → C, x 7→ f (x) :=
∅
dist(x, y) ≤ δ, y ∈ C
sonst
Anstelle eines einzelnen Klassifikators können auch mehrere Klassifikatoren verwendet werden,
die jeweils eine Abbildung auf Teilmengen vornehmen. Die letztendliche Abbildung wird durch
einen zusammengesetzten Klassifikator vorgenommen, der die entgültige Abbildung durch die
Kombinationsfunktion σ()bestimmt:
(
y
fn : R → Cn , x 7→ fn (x) :=
∅
d
dist(x, y) ≤ δ, y ∈ C
sonst
fN : Rd → C, x 7→ fN (x) := σ(f1 (x), ..., fn (x))
Diese Vorgehensweise ist dann interessant, wenn dadurch die Komplexität der Berechnung jedes einzelnen Klassifikators gesenkt werden kann.
14.1 Hierarchische Kohonen-Karten
Das Trainieren von Kohonen-Karten mit großer Eingabeschicht, großer Kohonen-Schicht und
vielen Trainingsmustern ist sehr Rechen- und damit Zeitaufwändig. Eine Parallelisierung kann
durch Zerlegung der Karte in kleinere Karten erfolgen, die unabhängig voneinander berechnet
werden können, so dass ein paralleler Algorithmus nicht notwendig ist. Gleichzeitig sinkt der
Rechenaufwand pro Karte, da weniger Neuronen und damit weniger Gewichtsvektoren vorhanden sind.
14.1.1 Konzept
Diese Herangehensweise wird dadurch motiviert, dass während des Trainings einer KohonenKarte tatsächlich zu Beginn eine derartige grobe Aufteilung stattfindet, wie in Abbildung 21 zu
sehen ist. Die Abbildung zeigt eine U-Matrix-Darstellung einer Karte in verschiedenen Stadien
des Trainings. Neuronen, deren Gewichtsvektoren sich stark von denen benachbarter Neuronen
unterscheiden, sind dunkel dargestellt, Cluster sind als helle Gebiete zu erkennen. Es ist deutlich
zu sehen, dass sich zu Beginn mehrere sehr großflächige Bereiche herausgebildet haben.
Bei diesem Ansatz sind jedoch folgende Faktoren zu berücksichtigen:
- 61 -
Abbildung 21: U-Matrix einer Kohonen-Karte
(a) Initiale Karte
(b) 2 Trainingszyklen
(c) 5 Trainingszyklen
(d) 10 Trainingszyklen
Quelle: Eigene Darstellung.
1. Das Aufteilen der Trainingsmuster muss so erfolgen, dass die Teilkarten nur mit den Mustern
trainiert werden, die für sie relevant sind. Es ist also eine Vorauswahl zu treffen.
2. Durch die Aufteilung der Karte könnten Nachbarschaftsbeziehungen zwischen Clustern gestört werden. Läge ein Gewinnerneuron am Rand einer Teilkarte, würde es bei entsprechend
Großem Trainingsradius auch Neuronen benachbarter Karten beeinflussen müssen.
Laut Punkt 1. stellt sich zunächst die Frage, wie die Datensätze auf die einzelnen Teil-Karten
zu verteilen sind, da genau dies - die Gruppierung ähnlicher Datensätze - ja eigentlich durch die
Karte selbst erfolgen soll. Hierfür bietet sich ein mehrstufiges Clustern an, bei dem zunächst
eine grobe Aufteilung der Datensätze P durch eine sehr kleine Kohonen-Karte mit wenigen
Neuronen vorgenommen wird. Anschließend werden für jeden Cluster bzw. jedes Neuron n
eine neue Karte erzeugt und mit den diesem Cluster zugeordneten Trainingsmustern Pn ⊂
P, dist(P, Wn ) → min trainiert.
Punkt 2. ist schwieriger zu Lösen. Ein Ansatz wäre, die Teilkarten erst dann zu erzeugen, wenn
der Trainingsradius klein genug ist, dass die Distanzfunktion hcj (t) Werte nahe Null liefert und
somit der zweite Summand der Lernfunktion ebenfalls gegen Null geht:
Wj (t + 1) = Wj (t) + η(t)hcj (t)[X(t) − Wj (t)]
Um beim späteren Zusammenfügen der Karten ein konsistentes Kartenbild zu erhalten, müssen
die Teilkarten eine Gewichtung der Neuronen aufweisen, die die Nachbarschaftsbeziehung so
abbildet, dass Neuronen an den Rändern benachbarter Karten ähnlich sind. Dies kann durch
- 62 -
Abbildung 22: Aufteilung einer SOM
Quelle: Eigene Darstellung.
Interpolation der Gewichtsvektoren erreicht werden. Wie später gezeigt wird, hat jedoch keinen Einfluss auf die Abbildungsqualität der Karte, kann jedoch das Identifizieren von Clustern
erleichtern, die aus mehreren Neuronen bestehen, da so die Nachbarschaftsbeziehungen gewährleistet werden.
Abbildung 23: Interpolation der Gewichte
Quelle: Eigene Darstellung.
Sei K die ursprüngliche Kohonen-Karte und K(x,y) die Teilkarte für das Neuron n ∈ K mit
den Dimensionen sx und sy. Für jedes Neuron n̄ ∈ K(x,y) kann durch Interpolation ein initialer
Gewichtsvektor ermittelt werden. Es ist dafür eine Abstandsfunktion ∆(n̄, n) für jedes Neurons n̄ entsprechend dessen Abstandes zu den Neuronen in K sowie eine Gewichtungsfunktion
f (n̄, n) notwendig, um die Gewichtsvektoren entsprechen der Entfernung zwischen den Neuronen unterschiedlich stark zu berücksichtigen. Dabei ist zu Beachten, dass K und K̄ verschiedene
- 63 -
Koordinatensysteme verwenden. Die entsprechende Abstandsfunktion ∆(n̄, n) für den Euklidischen Abstand lautet:
s
∆(n̄, n) =
1
xn̄
+ xK +
− xn
2
sx
2
+
1
yn̄
1
+ yK + 2 − yn
2
sy
2
Als Gewichtungsfunktion kann eine beliebige stetige, monoton fallende Funktion verwendet
werden, zum Beispiel eine quadratisch fallende Funktion der Form
f (n̄, n) =
1
∆(n̄, n)
oder die Gauss’sche Distanzfunktion, die häufig zur Bestimmung des Nachbarschaftsradius verwendet wird:
f (n̄, n) = e−(
∆(n̄,n) 2
d
)
Der interpolierte Gewichtsvektor ergibt sich somit aus:
i<N
1X
W̄n̄ =
Wi ∗ f (n̄, ni )
N i=0
Eine andere Möglichkeit ist die von Kohonen vorgeschlagene lokale Interpolation für wachsende SOMs, die jeweils nur die direkt benachbarten Neuronen von K(x,y) in K berücksichtigt.66
14.1.2 Umsetzung
Ist eine geeignete nicht-interaktive Umgebung zur Simulation Neuronaler Netze vorhanden,
lässt sich dieses Konzept vollständig mit Hilfe der Verfügbaren Tools sowie der Shell-SkriptProgrammierung lösen. Hier wird das Konzept anhand des SNNS und der mitgelieferten Tools
gezeigt. Hierfür sind folgende Komponenten notwendig:
1. Erzeugen der Kohonen-Karten
2. Trainieren der Kohonen-Karte
3. Bestimmung der Gewinnerneuronen für jedes Trainingsmuster
4. Aufteilung der Trainingsmuster
Der SNNS arbeitet mit Dateien für die Ein- und Ausgabe sowie Konfiguration. Wenn im Folgenden von der Erzeugung von Mustern oder Netzen die Rede ist, so ist damit das Generieren
einer entsprechenden Datei gemeint. Relevant sind hier die folgenden Dateiformate:
• .pat : Pattern-Dateien enthalten Trainings- und Testmuster.
• .net : Netz-Datei enthalten eine Beschreibung eines Neuronalen Netzes inklusive Topologie,
Verbindungen, Aktivierungs- und Lernfunktionen.
66
Siehe [KOHONEN 2001] S. 172f.
- 64 -
• .res : Result-Dateien sind ähnlich wie Pattern-Dateien aufgebaut und können Muster für
Input, Teaching-Output sowie Output enthalten.
• .batchman : Skript-Dateien stellen die Eingabe für den SNNS-Batch-Prozessor dar, mit dem
sich der SNNS per Kommandozeile bedienen lässt.
Erzeugen von Kohonen-Karten
Eine Kohonen-Karte lässt sich leicht mit dem Tool ff_bignet erzeugen. Der Aufruf zum Erzeugen einer Karte mit einer Eingabeschicht der Dimension [X1,Y1] sowie einer Kohonen-Schicht
der Dimension [X2,Y2] lautet:
$ff_bignet -p X1 Y1 Act_Identity Out_Identity input \
-p X2 Y2 Act_Identity Out_Identity hidden \
-l 1 0 0 0 0 0 0 0 0 2 0 0 0 0 0 0 0 0 DATEINAME.net
Das Kommando wurde zur besseren Benutzbarkeit durch ein Shell-Skript create_kohonen.sh
gekapselt. Die Syntax für den Aufruf lautet:
$create_kohonen.sh #Inputs X-Groesse Y-Groesse Dateiname
Als Aktivierungsfunktion der Kartenschicht wurde die Identität (Act_Identity) gewählt. Die Euklidische Distanz (Act_Euclid) als Aktivierungsfunktion ist sowohl ungeeignet als auch nutzlos.
Dies hat folgende Gründe:
1. Der SNNS bestimmt während des Trainings das Gewinnerneuron der Kartenschicht anhand
des maximalen Skalarproduktes aus Eingabevektor und Gewichtsvektor, die Aktivierungsfunktion wird dabei ignoriert. Dieses Verhalten entspricht der Verwendung von Act_Identity.
Die Funktion Act_Euclid existiert lediglich zum Zweck der Visualisierung durch die Grafische Oberfläche XGUI.
2. Um die SOM in ein Feed-Forward-Netz umzuwandeln, wird eine Aktivierungsfunktion benötigt, die dem Neuron mit dem geringsten Euklidischen Abstand zum Eingabevektor die
höchste Aktivierung zuweist. Act_Euclid würde hier stattdessen die geringste Aktivierung
liefern und ist somit ungeeignet.
3. Es wurde festgestellt, dass der Bias-Wert während des Trainings extrem hohe Werte erreichen kann, obwohl eine Anpassung des Bias in der Theorie der Kohonen-Karten nicht vorgesehen ist. Der SNNS-Quellcode zeigt, dass der Bias-Wert als Zähler dafür genutzt wird, wie
häufig ein Neuron Gewinnerneuron war. Daher müssen entweder alle Bias-Werte nach dem
Training explizit auf 0,0 gesetzt werden, oder es wird eine Aktivierungsfunktion verwendet,
die den Bias-Wert nicht benutzt, was bei Act_Identity der Fall ist.
Trainieren der Kohonen-Karte
Für das Trainieren einer Karte ist ein entsprechendes Batchman-Skript notwendig, dass die notwendigen Dateien lädt, die Trainingsfunktion konfiguriert und die Trainingszyklen durchführt
(Auszug):
loadNet(infile)
loadPattern(patfile)
setInitFunc("Randomize_Weights",-1.0,1.0)
initNet()
- 65 -
setLearnFunc("Kohonen",0.8 ,radius, factor ,factor, width)
setUpdateFunc("Kohonen_Order")
setShuffle(TRUE)
while radius>0.7 do
trainNet()
radius=radius*(factor^PAT)
endwhile
saveNet(outfile)
Dieses Skript verwendet Variablen, deren Werte jedoch im Skript fest definiert sein müssen, weil
Batchman keine Möglichkeit bietet, diese zur Laufzeit festzulegen und z.B. durch Kommandozeilen-Parameter zu übergeben. Da verschiedene Kohonen-Karten verwendet werden und daher
verschiedene Werte für die Trainingsfunktion notwendig sind, ist es ratsam, das Skript dynamisch zu generieren. Dies wurde durch das Skript train_kohonen.sh realisiert. Die Syntax für
den Aufruf lautet:
$train_kohonen.sh Netz-Datei Ausgabe-Datei Pattern-Datei \
Kartenbreite Zyklen Faktor
Bestimmung der Gewinnerneuronen
Nachdem eine Karte trainiert wurde, müssen für jedes Trainingsmuster das Gewinnerneuron ermittelt werden, dass den Cluster repräsentiert, dem das Trainingsmuster zugeordnet wurde. Der
SNNS ist in der Lage, eine Result-Datei zu generieren, die für jedes Trainingsmuster die Aktivierungswerte aller Ausgabeneuronen enthält. Das Problem ist, dass eine Kohonen-Karte im
SNNS keine Ausgabeneuronen besitzt, sondern die Karten-Schicht aus Versteckten Neuronen
(Hidden Layer) besteht.
Wenn man jedoch alle Versteckten-Neuronen in Ausgabe-Neuronen umwandelt, erhält man
ein einschichtiges Feed-Forward-Netz, womit sich dann problemlos die besagte Result-Datei
erzeugen lässt. Hierfür ist es lediglich Notwendig, die Definition des Neuronen-Typs in der
Netzwerk-Datei zu verändern, was sich mit einem einfachen Kommando bewerkstelligen lässt:
$sed ’s/| h/| o/g’ < infile.net > outfile.net
Anschließend kann wie schon beim Training der Kohonen-Karte ein Batchman-Skript erstellt
werden, welches Netz- und Patterndatei lädt und die Erzeugung der Result-Datei veranlasst.
Auch hier ist es ratsam, das Skript dynamisch zu erzeugen.
loadNet(netfile)
loadPattern(patfile)
setUpdateFunc("Topological_Order")
setShuffle(FALSE)
saveResult(resfile, 1, PAT, TRUE, FALSE, "create")
Das hier beschriebene Vorgehen wird durch das Shell-Skript apply_kohonen.sh realisiert. Die
Syntax für den Aufruf lautet:
$apply_kohonen.sh network.net pattern.net result.res
Anschließend müssen aus den Ausgabemustern in der Result-Datei die entsprechenden Gewinnerneuronen bestimmt werden. Das fehlerfreie Parsen einer beliebigen Result-Datei erfordert
- 66 -
jedoch ein komplizierteres Skript, so dass hier auf das Tool cnv_res2sxml aus dem SXMLSNNS-Interface67 zurückgegriffen wird. Da dieses XML-Dateien erzeugt, müssen diese noch
in CSV-Dateien umgewandelt werden.
$cnv_res2sxml mod=number input=false teaching=false \
output=false file=pattern0.res -nostdin | mod_process -f \
xml2csv.sxml 2>/dev/null | grep -v winner > pattern.csv
Die so erzeugte Datei pattern.csv enthält für jedes Muster eine Zeile mit der Nummer des Gewinnerneurons. Mittels
$grep -n $a < pattern.csv | cut -f1 -d":" > subset0_$a.sub
kann daraus eine Datei mit der Nummer aller Trainingsmuster erzeugt werden, die als Gewinnerneuron den Wert von $a haben. Diese Datei kann als Eingabe für das Tool pat_sel_simple
verwendet werden, um aus der ursprünglichen Pattern-Datei eine Untermenge von Mustern zu
bilden. Damit ist die Aufteilung der Trainingsmuster abgeschlossen.
Erzeugen von Teilkarten
Für jede Untermenge an Trainingsmustern kann nun erneut eine Kohonen-Karte erstellt und
trainiert werden. Dies kann durch das oben genannte Skript create_kohonen.sh erfolgen oder
aber es wird das Tool net_subdiv aus dem SXML-SNNS-Interface zurückgegriffen, welches für
ein gewähltes Neuron eine interpolierte Karte erzeugt:
$net_subdiv som.net #neuron X-Groesse Y-Groesse > subnet.net
14.1.3 Testaufbau
Für eine Beurteilung des Verfahrens ist ein Vergleich der hierarchischen Kohonen-Karte gegenüber einer normalen Kohonen-Karte unter kontrollierten Bedingungen Voraussetzung. Folgende
Parameter (SNNS-Pendant in Klammern) können dabei variiert werden:
• die verwendete Kartengröße und initiale Gewichtung
• Eigenschaften und Umfang der Trainingsdaten
• der Lernfaktor η (Adption height h(0))
• die Distanzfunktion h (Adaption radius r(0))
• die Anzahl der Lernzyklen
Die Qualität einer SOM bezüglich einer Menge an Eingabevektoren lässt sich anhand der Zuordungsgenauigkeit der Eingabevektoren zu Clustern bestimmen. Dazu kann der gemittelte Euklidische Abstand aller Eingabevektoren I zu den jeweiligen Gewichtsvektoren W der Gewinnerneuronen ermittelt werden:
pPn
∆(I, W ) =
i=0 (Ii
− Wi )2
n
Das Resultat allein stellt noch keine absolute Aussage über die Qualität dar, kann jedoch als
Vergleichswert dienen.
67
Siehe dazu [W ISSUWA], [W ISSUWA 2003]; Die Bibliothek ist im Quellcode auf der beiliegenden CD enthalten.
- 67 -
Algorithm 5 Hierarchische SOM
1
2
3
4
5
6
7
8
9
10
11
12
INPUTS=10
SX=4
SY=1
SX2=30
SY2=25
CYCLES1=50000
CYCLES2=100000
SUBS=""; for (( a=0; a<SX*SY; a++ )); do SUBS=$SUBS"$a "; done
./create_kohonen.sh $INPUTS $SX $SY som_untrained.net
./train_kohonen.sh som_untrained.net som_trained.net pattern0.pat $SX
$CYCLES1
13 ./apply_kohonen.sh som_trained.net pattern0.pat pattern0.res
14
15 /usr/local/sxml/bin/cnv_res2sxml mod=number input=false teaching=false
output=false file=pattern0.res -nostdin | /usr/local/sxml/bin/
mod_process -f xml2csv.sxml 2>/dev/null | grep -v winner > pattern0.csv
16
17 for a in $SUBS ; do
18
grep -n $a < pattern0.csv | cut -f1 -d":" > subset0_$a.sub
19
if test -e pattern1_$a.pat; then rm pattern1_$a.pat; fi
20
/usr/local/SNNS/bin/pat_sel_simple subset0_$a.sub pattern0.pat
pattern1_$a.pat
21 done
22
23 for a in $SUBS ; do
24
./create_kohonen.sh 10 $SX2 $SY2 subnet0_$a.net
25 done
26
27 for a in $SUBS ; do
28
if test -s subset0_$a.sub ; then
29
./train_kohonen.sh subnet0_$a.net subnet_trained_0_$a.net pattern1_$a.
pat $SX2 $CYCLES2 &
30
else
31
cp subnet0_$a.net subnet_trained_0_$a.net
32
echo "no patterns for subnet $a."
33
fi
34 done
35
36 wait
37
38 Nets="";
39 for a in $SUBS ; do
40
Nets=$Nets"subnet_trained_0_$a.net "
41 done
42
43 /usr/local/sxml/bin/net_merge $SX $Nets > som_trained_1.net
- 68 -
Kohonen-Karte Die verwendeten Karten variieren in der Größe der Kartenschicht, besitzen jedoch stets die selbe Anzahl von Eingabeneuronen. Für die hierarchische SOM wird die
Kartenschicht in 3 mal 2 Teilkarten aufgeteilt. Die Initialisierung der Karten erfolgt mit einem
konstanten Seed-Wert von 1.
Trainingsdaten Die Trainingsmenge umfasst 1000 Vektoren mit 10 Elementen im Wertebereich [0.0-1.0]. In der Trainingsmenge wurden zunächst zufällig 20 Clusterzentren erzeugt.
Diese wurden entsprechend häufig dupliziert und mit zufälligen Abweichungen im Bereich
[±0.04] versehen.
Lernfaktor Zunächst ist ein geeigneter Lernfaktor zu bestimmen. Um den Einfluss des Lernfaktors auf die Clusterqualität zu untersuchen, wurden Karten verschiedener Größe mit verschiedenen Lernfaktoren erzeugt68 . Wie die Abbildung 24 zeigt, ist die Wahl der Lernrate
hauptsächlich für die hierarchische SOM relevant. Hier werden mit sehr kleinen Werten für
η die besten Resultate erzielt. Daher wurde für alle Experimente eine konstante Lernrate von
η = 0.01 festgelegt.
Abbildung 24: Clusterqualität vs. Lernrate / Kartengröße
Clusterqualität bei 10 Trainingszyklen mit je 1000 Eingabemustern. Die parallele SOM
besteht aus 6 Teilkarten.
Quelle: Eigene Darstellung.
Distanzfunktion Als Distanzfunktion wird normalerweise die Gaussche-Distanzfunktion
verwendet. Der SNNS jedoch Simuliert den Effekt dieser Funktion durch eine Reduzierung des
Lernradius nach jeder Iteration69 . Als initialer Lernradius wird die Breite der Kohonen-Schicht
68
69
Die Messwerte sind in den Tabellen 9 und 10 im Anhang B.2 aufgeführt.
Siehe [Z ELL 2000] S.72f.
- 69 -
verwendet. Der Faktor zur Anpassung des Lernradius nach der Präsentation jedes einzelnen
Eingabevektors wird in Abhängigkeit von der Anzahl der Eingabemuster nI und der Trainingszyklen nT so berechnet, dass der Lernradius nach Ablauf aller Iterationen n = nI ∗ nT den Wert
1.0 erreicht:
1.0 = r ∗ multRn
r
multR =
n
1)
ln( r
1
= e n
r
Anzahl der Lernzyklen Es werden, sofern nicht anders angegeben, stets 50000 Lernschritte
durchlaufen.
14.1.4 Vergleich der Modellqualität
Durch eine visuelle Auswertung lassen sich erste Schlüsse auf die Eigenschaften einer KohonenKarte ziehen. Abbildung 25 und 26 zeigen jeweils eine U-Matrix-Darstellung von KohonenKarten identischer Dimension nach jeweils 50000 Lernschritten. Die Hierarchische Karte besteht aus vier Teilkarten. Die unterschiedliche Anzahl von erkennbaren Cluster-Bereichen pro
Teilkarte ist darauf zurückzuführen, dass die Aufteilung der Trainingsdaten nicht gleichmäßig
erfolgte.
Es ist zu erkennen, dass beide Karten entsprechend der Clusteranzahl in den Trainingsmustern
(mindestens) 20 Cluster-Bereiche enthalten. Während die normale Karte exakt die in den Daten
enthaltenen 20 Cluster darstellt, ist die Anzahl bei der Hierarchischen Karte etwas höher. Es
ist zu vermuten, dass einige Cluster-Bereiche den selben Cluster repräsentieren. Die U-Matrix
der normalen Karte zeigt, dass einige Cluster-Bereiche sehr ähnlich sind, da die Clustergrenzen
relativ schwach ausgeprägt sind. Die zugehörigen Trainingsmuster können bei der Aufteilung
benachbarten Teilkarten der hierarchischen SOM zugewiesen werden, wo sie eigene Cluster
bilden und so die Gesamtzahl von Cluster-Bereichen erhöhen. Dies ist jedoch eher ein optisches
Problem, denn die Zugehörigkeit von Kartenneuronen zu Clustern wird letztendlich durch die
Ähnlichkeit der Gewichtsvektoren und nicht durch die Position auf der Karte bestimmt.
Da die Clusterqualität von der Größe der Karte abhängt, wurden Versuche mit unterschiedlichen Kartengrößen durchgeführt. Das Resultat ist in Abbildung 27 dargestellt. Wie zu erwarten
liefern beide Karten relativ schlechte Ergebnisse, wenn die Anzahl der Neuronen nicht für die
abzubildenden Cluster ausreicht. Die Hierarchische Karte ist sogar noch etwas schlechter, was
dadurch bedingt ist, dass durch die Aufteilung der Karte die Neuronenzahl weiter abnimmt. Bei
größeren Karten jedoch liefert die Hierarchische Version deutlich bessere Ergebnisse.
- 70 -
Abbildung 25: U-Matrix Seriell
Quelle: Eigene Darstellung.
Abbildung 26: U-Matrix Hierarchisch
Quelle: Eigene Darstellung.
Abbildung 27: Clusterqualität
Clusterqualität
Quelle: Eigene Darstellung.
- 71 -
14.1.5 Vergleich der Rechengeschwindigkeit
Die untersuchten Karten haben eine Größe von 30x30 Neuronen. Die Hierarchische Karte besteht aus 6 Teilkarten a 10x15 Neuronen.
Für die Beurteilung der Rechengeschwindigkeit wurden drei Einsatzszenarien untersucht:
1. Serielle Karte auf einem Prozessor.
2. Hierarchische Karte auf einem Prozessor.
3. Hierarchische Karte auf mehreren Prozessoren.
Die Zeitmessung umfasst stets den gesamten unter 14.1.2 dargestellten Prozess vom Erzeugen
der untrainierten Karten bis zur Zusammensetzung der Teilkarten zu einer Gesamtkarte. Es
wurden jeweils die real vergangene Zeit und die Prozessorzeit für verschiedene Kartengrößen
gemessen. Das Resultat ist in Abbildung 28 dargestellt. Die hierarchische Karte ist, selbst wenn
sie nicht parallel Berechnet wird, bereits ab einer sehr geringen Größe der Ausgabeschicht von
ca. 100 Neuronen deutlich schneller als die serielle Version. Da der Aufwand zur Berechnung
der Abstände und Gewichte einer Kohonen-Schicht mit n Neuronen O(n) = n2 ist, kann durch
2
2
Zerlegung der Karte in p Teilkarten der Aufwand auf O(n) = p ∗ np = np gesenkt werden.
Der Speedup der parallelen gegenüber der nicht-parallelen Berechnung der hierarchischen SOM
fällt relativ gering aus. Dies ist darauf zurückzuführen, dass neben der eigentlichen Berechnung
viel Rechenzeit für die Erzeugung der Musterdateien und die Auswertung der Teilkarten beansprucht wird, da dies hauptsächlich durch vergleichsweise langsame Shell-Skripte realisiert
wurde.
Abbildung 28: Speedup SOM Parallel / Seriell
Quelle: Eigene Darstellung.
- 72 -
14.2 Feed-Forward-Netze
14.2.1 Konzept
Analog zur hierarchischen SOM kann auch bei Feed-Forward-Netzen eine Parallelisierung
durch Anpassung der Netztopologie erfolgen. Dies ist jedoch Abhängig von der Codierung
und der Anzahl der Ausgabemuster. Für den Fall, dass ein n-fach-Klassifikator erzeugt werden
soll, kann dies durch Verwendung von n binären Klassifikatoren erreicht werden, die jeweils
separat berechnet werden können. Der Rechenaufwand reduziert sich jedoch entsprechend der
Anzahl der Verbindungen maximal um den Faktor n. Eine Aufteilung der Trainingsmuster und
damit eine Reduzierung der Trainingszyklen ist nicht möglich.
Die Zahl der Rechenschritte p für einen Propagierungsschritt in einem mehrschichtigen Netz
mit n voll vernetzten Schichten L ist:
p=
n−1
X
Li ∗ Li+1
i=0
Durch Bildung separater Netze für jedes Ausgabeneuron in Ln reduziert sich die Anzahl der
zu berechnenden Verbindungen in der letzten Schicht, gleichzeitig steigt die Anzahl der zu
berechnenden Netze, so dass für p in diesem Fall gilt:
p = Ln ∗ (Ln−2 +
n−2
X
Li ∗ Li+1 )
i=0
Für ein 10x10x20-Netz ist die Anzahl der zu Berechnenden Verbindungsgewichte 300, nach
Reduktion auf ein 10x10x1-Netz nur noch 110. Da jedoch 20 solcher Netze trainiert werden
müssen, steigt die Gesamtzahl auf 2200 Rechenschritte. Dieses Verfahren ist also nur dann
sinnvoll, wenn die Berechnung wirklich parallel erfolgen kann. In diesem Fall ist eine Geschwindigkeitssteigerung um den Faktor
Pn−1
i=0 Li ∗ Li+1
S=
P
Ln−2 + n−2
i=0 Li ∗ Li+1
zu erwarten.
14.2.2 Umsetzung
Die Trainingsmenge umfasst 1000 Vektoren mit 10 Elementen im Wertebereich [0.0-1.0]. Die
Vektoren sind jeweils einer von 20 Klassen zugeordnet, die erkannt werden sollen. Dazu wurde
in der Trainingsmenge wurden zufällig 20 Referenzvektoren erzeugt, die entsprechend häufig
dupliziert und mit zufälligen Abweichungen im Bereich ±0.2 versehen wurden. Die Codierung
des Teaching-Output erfolgte so, dass für jede Klasse C das n-te Element aktiviert und die
restlichen Elemente deaktiviert wurden:
(
1, 0 n = C
tn =
0, 0 sonst
- 73 -
Für die binären Klassifikatoren wurde eine ähnliche Kodierung verwendet. Da jeder Klassifikator für die Unterscheidung zwischen C und ¬C trainiert wird, lautet die Kodierung:
(
[1, 0; 0, 0] C
t=
[0, 0; 1, 0] ¬C
14.2.3 Vergleich der Modellqualität
Zunächst ist die Topologie des zu verwendenden Netzes, insbesondere die Größe der versteckten Schicht zu ermitteln. Hierzu wurden Netze mit versteckten Schichten unterschiedlicher Größe jeweils so lange trainiert, bis der Fehlerwert klein genug war. Als Basis für den Fehlerwert
dient der Summierte Quadratische Fehler (SSE70 ). Für eine Trainingsmenge P und die AusgabeNeuronen O ist dieser definiert als
SSE =
XX
(tij − oj )2
i∈P j∈O
Basierend darauf lassen sich der mittlere Fehler über alle Ausgabeneuronen SSE/O und der
mittlere Fehler über alle Trainingsmuster MSE=SSE/P ableiten. Als genereller Indikator für
den Trainingsfortschritt ist der MSE am besten geeignet, da er die Anzahl der Trainingsmuster
berücksichtigt.
Da das parallele Modell eine andere Topologie und eine andere Kodierung des Teaching-Output
aufweist, sind geeignete Kriterien notwendig, um sowohl die Erkennungsqualität als auch die
benötigte Rechenzeit vergleichbar zu machen. Da es das Ziel ist, eine möglichst hohe Erkennungsrate der Trainingsmuster zu erzielen, ist es sinnvoll, den jeweiligen Netzfehler als Indikator heranzuziehen und das Training zu beenden, sobald z.B. der MSE < 0.008 ist. Um diesen
Wert zu erreichen, benötigt der n-fach-Klassifikator eine bestimmte Anzahl von Trainingszyklen c und eine bestimmte Rechenzeit t. Die Verwendung binärer Klassifikatoren ist sinnvoll,
sobald t und im optimalen Fall auch c unterschritten werden.
Die Bewertung der erstellten Netze wurde mittels des SNNS-Tools analyze durchgeführt. Als
Analysefunktion wurde „WTA” gewählt, da die Klassenzugehörigkeit durch genau ein aktives
Neuron repräsentiert wird.
In Tabelle 14 sind die Erkennungsraten, Rechenzeiten und Trainingszyklen für einen n-FachKlassifikator und einen Binären Klassifikator für verschiedene große Zwischenschichten gegenübergestellt. Beim Binären Klassifikator wurde die Erkennungsgenauigkeit des jeweils schlechtesten Teilklassifikators zugrundegelegt. Als Vergleich sind die Erkennungsraten und Trainingszyklen aller Teilklassifikatoren für eine Zwischenschicht mit einem Neuron in Tabelle 13 aufgeführt.
Der n-Fach-Klassifikator liefert erst ab einer Zwischenschicht mit 5 Neuronen akzeptable Werte, wogegen der Binäre Klassifikator bereits mit nur einem Zwischenneuron eine hohe Erkennungsrate erzielt. Eine Steigerung der Neuronenzahl bewirkt hier nur marginale Veränderungen.
70
Der SNNS und das Tool analyze geben für das selbe Netz und die selben Muster für den SSE leicht abweichende
Werte an.
- 74 -
14.2.4 Vergleich der Rechengeschwindigkeit
Die Rechenzeiten sind Tabelle 14 zu entnehmen. Es wurden jeweils 3 Messungen durchgeführt
und die kürzeste Rechenzeit gewertet.
Die Rechenzeiten des n-Fach-Klassifikators und des binären Klassifikators lassen sich hinsichtlich zweier Kriterien miteinander verleichen: ähnliche Topologie und ähnliche Resultate.
Beim Vergleich der Klassifikatoren mit ähnlicher Topologie bzw. gleicher Größe der Zwischenschicht reduziert sich der Speedup von ≈ 6, 8 bei 5 Zwischenneuronen auf ≈ 1, 25 ab 7 Zwischenneuronen.
Werden die jeweils kleinsten Klassifikatoren verglichen, die vergleichbare Resultaten liefern,
so kann ein deutlich höherer Geschwindigkeitszuwachs festgestellt werden. So erreicht der binäre Klassifikator bereits mit nur zwei Zwischenneuronen eine Erkennungsrate von 99,00 %.
Der n-Fach-Klassifikator benötigt hierfür mindestens fünf Zwischenneuronen und eine deutlich
höhere Berechnungszeit. Der Speedup beträgt in diesem Fall ≈ 7, 4.
- 75 -
Teil IV
Zusammenfassung und Ausblick
In dieser Arbeit wurden verschiedene Ansätze zur Parallelisierung von Algorithmen vorgestellt.
Diese bezogen sich sowohl auf die Anpassung des Algorithmus selbst durch Verwendung paralleler Programmierkonzepte, als auch auf die Parallelisierung durch Aufteilung der Rechenaufgaben unter Verwendung serieller Programme. Es wurde gezeigt, dass Parallele Programmierung ein sehr komplexes Thema ist, das eine enge Verzahnung von Hardware, Software und
Anwendungsgebiet auf verschiedenen Ebenen aufweist.
Der aktuelle Stand der Technik wird durch MPI/MPI2 und OpenMP festgelegt. Beide Ansätze
sind ausgereift, flexibel, relativ einfach zu handhaben und liefern sehr gute Resultate. MPI hat
sich dabei als besonders performant erwiesen, während OpenMP sehr leicht auf bestehende
Programme anzuwenden ist. UPC hingegen muss anhand der gemachten Erfahrungen als rein
Experimentell angesehen werden.
Die Entscheidung, ob MPI oder OpenMP das Mittel der Wahl sind, hängt im wesentlichen von
den Faktoren Wiederverwendung und Skalierbarkeit ab. MPI erfordert einen höheren Programmieraufwand, ist dafür jedoch auch in sehr großen, netzwerkbasierten und heterogenen Rechnerumgebungen einsetzbar, während OpenMP-basierte Programme naturgemäß nur auf einem
einzigen System ausführbar sind, sofern das Betriebssystem nicht die Migration von Prozessen
über Rechenknoten hinweg unterstützt. Da MPI und OpenMP miteinander Kombiniert werden
können und beispielsweise MPI für die Kommunikation zwischen Rechenknoten und OpenMP
für die parallele Ausführung auf einem Knoten eingesetzt wird, lassen sich hochgradig parallele
Anwendungen bei gleichzeitig überschaubarer Komplexität des Programmcodes implementieren.
Anhand der Parallelisierung von Data-Mining-Modellen mittels Basistechnologien von LinuxSystemen wurde gezeigt, dass ein signifikanter Geschwindigkeitsgewinn erzielt werden kann,
ohne den Algorithmus verändern zu müssen. Die vorgestellte Hierarchische Kohonen-Karte
sowie der auf mehreren Feed-Forward-Netzen basierende n-Fach-binäre Klassifikator erzielen
Resultate, die qualitativ mit der herkömmlichen Vorgehensweise der Erstellung eines Einzelmodells vergleichbar sind, jedoch deutlich schneller berechnet werden. Besonders die Hierarchische-Kohonen-Karte ist ein vielversprechender Ansatz, da auch bei nicht-paralleler Berechnung ein signifikanter Geschwindigkeitsgewinn erzielt wurde.
Diese Arbeit zeigt, dass Parallelisierung von Data-Mining-Verfahren selbst in geringem Maß
deutliche Geschwindigkeitsvorteile bringt. Die vorgestellten Ansätze bezogen sich dabei nur
auf einen Teilaspekt des Data Mining - der Erstellung von Modellen. Der gesamte Data-MiningProzess besteht jedoch aus mehreren Stufen, die jede für sich auf Parallelisierbarkeit untersucht
werden sollte. Insbesondere die zeitaufwändige Vorverarbeitung stellt ein geeignetes Ziel für
weiterführende Arbeiten dar.
- 76 -
Literatur
[UPC 2005] (2005). UPC Language Specification V1.2. The UPC Consortium.
[A LPAR 2000] A LPAR , PAUL (2000). Data Mining im praktischen Einsatz. Vieweg, München.
[BARTH 2006] BARTH , T HOMAS UND S CHÜLL , A NKE (2006). Grid Computing - Konzepte,
Technologien, Anwendungen. Vieweg, Wiesbaden.
[BAUKE 2006] BAUKE , H EIKE UND M ERTENS , S TEFAN (2006). Cluster Computing - Praktische Einführung in der Hochleistungsrechnen auf Linux-Clustern. Springer, Heidelberg.
[B RANDS 2005] B RANDS , G ILBERT (2005). Das C++ Kompendium. Springer.
[C ARVER 2006] C ARVER , R ICHARD H. UND TAI , K UO -C HUNG (2006). Modern Multithreading. Wiley.
[C HANDRA ] C HANDRA , ROHIT UND DAGUM , L EONARDO UND KOHR DAVE UND M AYDAN D ROR UND M C D ONALD J EFF UND M ENON R AMESH . Parallel Programming in
OpenMP. Morgan Kaufmann Publishers.
[C HAPMAN 2008] C HAPMAN , BARBARA UND J OST, G ABRIELE UND VAN DER PAS RUUD
(2008). Using OpenMP - Portable Shared Memory Parallel Programming. The MIT Press.
[C HAUVIN 2008] C HAUVIN , S ÈBASTIEN (2008). UPC Manual v1.2.
[C LEVE 2005] C LEVE , J ÜRGEN UND L ÄMMEL , U WE UND W ISSUWA S TEFAN (2005). Data
Mining auf zeitabhängigen Daten – Kundenanalyse im Bankbereich. Hochschule Wismar,
Wismar.
[E M K ARNIADAKIS 2000] E M K ARNIADAKIS , G EORGE UND K IRBY, ROBERT M. II (2000).
Parallel Scientific Computing in C++ and MPI. Cambridge University Press.
[E NGELN -M ÜLLGES 2005] E NGELN -M ÜLLGES , G ISELA UND N IEDERDRENK , K LAUS
UND W ODICKA R EINHARD (2005). Numerik-Algorithmen - Verfahren, Beispiele, Anwendungen.
[UND G ORDON S. L INOFF 2000] G ORDON S. L INOFF , M ICHAEL J. A. B ERRY UND (2000).
Mastering Data Mining. John Wiley and Sons, New York.
[G ROPP 2007] G ROPP, W ILLIAM UND L USK , E WING
MPI - Eine Einführung. Oldenbourg, München Wien.
UND
S KJELLUM A NTHONY (2007).
[H AN 2001] H AN , J IAWEI UND K AMBER , M ICHELINE (2001). Data Mining - Concepts and
Techniques. Morgan Kaufmann Publishers, San Francisco.
[H OFFMANN 2008] H OFFMANN , S IMON UND L IENHART, R AINER (2008). OpenMP - Eine
Einführung in die parallele Programmierung mit C/C++. Springer, Berlin Heidelberg.
[K NIPPERS 2001] K NIPPERS , ROLF (2001). Molekulare Genetik. Georg Thieme Verlag, Stuttgart.
[KOHONEN 2001] KOHONEN , T EUVO (2001). Self-Organizing Maps. Springer.
[L ÄMMEL 2007] L ÄMMEL , U WE UND B EIFERT, A NATOLI UND W ISSUWA S TEFAN (2007).
Business Rules - Die Wissensverarbeitung erreicht die Betriebswirtschaft. Wismarer Diskussionspapiere Heft 05/2007, Wismar.
[L ÄMMEL 2004] L ÄMMEL , U WE UND CL EVE , J ÜRGEN (2004). Künstliche Intelligenz. Fachbuchverlag Leipzig, Leipzig.
- 77 -
[L ÄMMEL 2004] L ÄMMEL , U WE UND C LEVE , J ÜRGEN (2004). Lehr- und Übungsbuch
Künstliche Intelligenz. Fachbuchverlag Leipzig, Leipzig.
[L ÄMMEL 2003] L ÄMMEL , U WE (2003). Data Mining mittels künstlicher neuronaler Netze,
WDP No. 7. Hochschule Wismar, Wismar.
[M AHLMANN 2007] M AHLMANN , P ETER UND S CHINDELHAUER , C HRISTIAN (2007). Peerto-Peer Netzwerke - Algorithmen und Methoden. Springer, Berlin Heidelberg.
[NAKHAEIZADEH 1998] NAKHAEIZADEH , G. (1998). Data Mining - Theoretische Aspekte
und Anwendungen. Physica-Verlag, Heidelberg.
[P ETERSEN 2004] P ETERSEN , W. P.
puting. Oxford University Press.
UND
A RBENZ , P. (2004). Introduction to Parallel Com-
[R AUBER 2007] R AUBER , T HOMAS UND RÜNGER , G UDULA (2007). Parallele Programmierung. Springer, Heidelberg.
[R AUBER 2008] R AUBER , T HOMAS UND RÜNGER , G UDULA (2008). Multicore: Parallele
Programmierung. Springer, Heidelberg.
[S ANTORO 2007] S ANTORO , N ICOLA (2007). Design and Analysis of Distributed Algorithms.
Wiley.
[S CHILL 2007] S CHILL , A LEXANDER UND S PRINGER , T HOMAS (2007). Verteilte Systeme Grundlagen und Basistechnologien. Springer, Berlin Heidelberg.
[W ISSUWA 2003] W ISSUWA , S TEFAN (2003). Data Mining und XML - Modularisierung und
Automatisierung von Verarbeitungsschritten, WDP No. 12. Hochschule Wismar, Wismar.
[W ISSUWA ] W ISSUWA , S TEFAN UND L ÄMMEL , U WE UND C LEVE J.
[W ITTEN 2001] W ITTEN , I.H. UND F RANK , E. (2001). Data Mining. Hanser, München.
[W ITTEN 2005] W ITTEN , I.H. UND F RANK , E. (2005). Data Mining: Practical Machine
Learning Tools and Techniques. Morgen Kaufmann, San Francisco.
[Z ELL 2000] Z ELL , A NDREAS (2000). Simulation Neuronaler Netze. Oldenbourg, München.
[Z ELL ] Z ELL , A NDREAS ET.
University of Tübingen.
AL .
SNNS User Manual, Version 4.2. University of Stuttgart,
- 78 -
Teil V
Ehrenwörtliche Erklärung
Ich erkläre hiermit ehrenwörtlich, dass ich die vorliegende Arbeit ohne unzulässige Hilfe Dritter und ohne Benutzung anderer als den angegebenen Hilfsmitteln angefertigt habe. Die aus
anderen Quellen direkt oder indirekt übernommenen Daten und Konzepte sind unter Angabe
der Quelle als solche gekennzeichnet.
Die Arbeit wurde bisher weder im In- noch im Ausland in gleicher oder ähnlicher Form einer
anderen Prüfungsbehörde vorgelegt.
Wismar, 01.12.2008.
- 79 -
Teil VI
Anhang
- 80 -
A Schnittstellen und Bibliotheken
A.1 UPC Konsortium
Mitglieder des UPC-Konsortiums nach[C HAUVIN 2008]:
UPC-Konsortium: National Security Agency (NSA - http://www.nsa.gov)
IDA Institute for Defense Analyses, Center for Computing Sciences (IDA-CCS - http://
www.super.org)
The George Washington Universtity, High Performance Computing Laboratory (GWU HPCL http://upc.gwu.edu)
Arctic Region Supercomputing Center (ARSC - http://www.arsc.edu); Hewlett-Packard
(HP - http://h30097.ww3.hp.com/upc/)
Cray Inc. (http://www.cray.com)
Etnus LLC. (http://www.etnus.com)
IBM (http://www.ibm.com)
Intrepid Technologies (http://www.intrepid.com)
Ernest Orlando Lawrence Berkley National Laboratory (LBNL - http://upc.lbl.gov)
Lawrence Livermore National Laboratory (LLNL - http://www.llnl.gov)
Michigan Technological University (MTU - http://upc.mtu.edu)
Silicon Graphics, Inc. (SGI - http://www.sgi.com)
Sun Microsystems (http://www.sun.com)
University of California, Berkley (http://www.berkley.edu)
US Department of Energy (DoE -http://www.energy.gov)
Ohio State University (OSU - http://www.osu.edu)
Argonne National Laboratory (ANL - http://www.anl.gov)
Sandia National Laboratory (http://www.sandia.gov)
University of North Carolina (UNC - http://www.unc.edu)
- 81 -
A.2 Compilerübersicht
Produkt
GCC UPC
Berkley UPC
Tabelle 2: Übersicht UPC-Compiler
Architektur / System
Anbieter
OS: Linux
Intrepid Technologc, Inc.,
Palo Alto, CA.,
CPU: x86_64 / ia64 / i686 / SGI
www.intrepid.com/
IRIX / Source
upc
OS: Linux, FreeBSD, NetBSD,
Tru64, AIX, IRIX, HPUX, Solaris,
Microsoft Windows, Mac OS X,
Cray Unicos, NEC SuperUX
LBNL, UC Berkley
upc.lbl.gov
CPU: x86, Itanium, Opteron,
Athlon, Alpha, PowerPC, MIPS,
PA-RISC, SPARC, Cray T3E,
Cray X1/X1E, Cray XD1,
Cray XT3, SX-6, SGI Altix
Compiler: GNU GCC, Intel C,
Portland Group C, SunPro C,
Compaq C, HP C, MIPSPro C,
IBM VisualAge C, Cray C,
NEC C, Pathscale C
MuPC UPC-to-C
HP UPC
IBM XL UPC
HP-UX ia64, HP-UX PA-RISC,
Linux ia64, Linux Opteron,
Tru64 UNIX
OS: AIX 5.2/5.3, SUSE 10,
Blue Gene/L
CPU: Alpha
- 82 -
Michigan Technological University
www.upc.mtu.edu
Hewlett-Packard
www.hp.com/go/upc
Registrierung erforderlich
IBM
www.alphaworks.
ibm.com/tech/
upccompiler
Produkt
gcc (4.2)
XL C/C++, Fortran
C/C++, Fortran
C/C++, Fortran
C/C++, Fortran
Fortran
C/C++, Fortran
C/C++, Fortran
C/C++, Fortran
Visual Studio 2008
C++
Tabelle 3: OpenMP-fähige Compiler
Architektur / System
Anbieter
OS: Linux, Solaris, AIX,
GNU
MacOSX, Windows
OS: Windows, AIX, Linux
IBM
OS: Solaris, Linux
Sun Microsystems
OS: Windows, Linux, MacOSX
Intel
Portland Goup Compilers
and Tools
Absoft Pro FortranMP
Lahey / Fujitsu Fortran 95
OS: Linux
PathScale
Hewlett-Packard
Microsoft
Quelle: http://openmp.org/wp/openmp-compilers/
- 83 -
B Messwerte
B.1 Messwerte Matrizenmultiplikation
M\P
1
2
10
25
50
75
100
150
200
250
300
350
400
450
500
550
600
0.000066
0.000486
0.003486
0.004511
0.027885
0.098130
0.236753
0.479782
0.896232
1.336331
2.522894
4.457177
6.193651
9.428142
9.182826
4
6
8
10
12
Tabelle 4: Matrizenmultiplikation Seriell
M\P
1
2
4
6
8
10
12
10
25
50
75
100
150
200
250
300
350
400
450
500
550
600
0.000345
0.005156
0.042337
0.151507
0.356807
1.275226
3.042646
5.853685
10.275381
16.344794
24.355682
34.658302
47.887960
62.819381
83.782705
0.000187
0.002717
0.021467
0.092006
0.173337
0.655847
1.528626
2.961748
5.169971
8.220303
12.276938
17.430334
24.080471
32.517365
42.148312
0.000127
0.001527
0.011224
0.039584
0.088143
0.327994
0.769294
1.498230
2.603281
4.159847
6.169760
8.777367
12.116765
16.459072
21.190994
0.000093
0.001069
0.007851
0.027183
0.059375
0.219487
0.521998
1.022699
1.798386
2.839749
4.309103
5.966676
8.128559
10.940555
14.289468
0.000090
0.000878
0.006020
0.037312
0.045220
0.166372
0.408651
0.760586
1.320743
2.154535
3.167183
4.456782
6.112915
8.379933
10.686609
0.000144
0.001291
0.013269
0.029618
0.051460
0.178803
0.332544
0.615677
1.094328
1.719209
2.499268
3.572334
4.877766
6.630831
8.798027
0.000283
0.003190
0.021087
0.044298
0.073834
0.163974
0.514728
0.547898
1.749707
1.521786
2.882435
3.032739
4.229874
6.716945
7.302215
Tabelle 5: Matrizenmultiplikation UPC
- 84 -
M\P
1
2
4
6
8
10
12
10
25
50
75
100
150
200
250
300
350
400
450
500
550
600
0.000061
0.000708
0.005523
0.018463
0.044097
0.148982
0.351516
0.689630
1.212470
2.266326
3.412175
5.918736
8.273327
11.395000
12.216489
0.000139
0.000495
0.002913
0.009502
0.022442
0.074534
0.177469
0.345555
0.613433
1.035687
1.739066
3.025944
4.225347
5.794530
6.473583
0.000215
0.000702
0.004236
0.009233
0.022083
0.072459
0.113075
0.189924
0.322143
0.564305
0.873463
1.547039
2.168703
2.930399
3.226290
0.000325
0.000847
0.004258
0.010174
0.021800
0.053846
0.086735
0.136494
0.252879
0.399579
0.622746
1.080466
1.509814
2.081293
2.224139
0.000405
0.000974
0.005080
0.011604
0.028045
0.054312
0.109039
0.161868
0.247536
0.354034
0.572927
0.821592
1.156219
1.586476
2.323643
0.000508
0.001115
0.005482
0.014458
0.027283
0.053527
0.113966
0.164122
0.183129
0.290627
0.452519
0.690350
0.950083
1.319551
1.386175
0.000667
0.001231
0.005587
0.015038
0.024235
0.068654
0.093838
0.139350
0.201306
0.329320
0.491077
0.806803
1.013034
1.534482
1.677829
Tabelle 6: Matrizenmultiplikation OpenMP
M\P
10
25
50
75
100
150
200
250
300
350
400
450
500
550
600
1
2
4
6
8
10
12
0.000327
0.000981
0.002040
0.005550
0.029358
0.040484
0.094581
0.467562
0.399043
1.583401
1.595673
4.474244
5.819763
8.026178
8.624467
0.000992
0.001326
0.002146
0.005491
0.008078
0.035740
0.055123
0.078360
0.139833
0.400853
0.476732
1.205406
2.047167
2.890131
2.649446
0.001667
0.001469
0.002225
0.003860
0.006612
0.018740
0.080043
0.077515
0.136844
0.230183
0.446546
0.885877
1.274201
1.788950
1.377678
0.002076
0.001433
0.001652
0.004270
0.004113
0.015662
0.033456
0.107663
0.106269
0.223505
0.351717
0.715220
0.956625
1.183636
1.306128
0.001054
0.002717
0.003668
0.002541
0.003911
0.013740
0.017963
0.061808
0.135504
0.195669
0.314738
0.560056
0.705274
0.875405
1.062164
0.001063
0.001988
0.003607
0.004839
0.004692
0.013497
0.044365
0.037410
0.102665
0.180105
0.243988
0.422473
0.383832
0.828765
0.890537
Tabelle 7: Matrizenmultiplikation MPI Master-Worker
- 85 -
M\P
1
2
4
6
8
10
12
10
25
50
75
100
150
200
250
300
350
400
450
500
550
600
0.000066
0.000486
0.003486
0.004511
0.027885
0.098130
0.236753
0.479782
0.896232
1.336331
2.522894
4.457177
6.193651
9.428142
9.182826
0.000207
0.000542
0.002114
0.006303
0.014624
0.050182
0.116972
0.252491
0.409708
0.395171
1.268596
2.236218
3.754630
4.700820
4.207593
0.001851
0.001714
0.002658
0.005153
0.005050
0.026883
0.062404
0.119238
0.226100
0.416644
0.664815
1.263629
1.631193
2.522061
2.407333
0.001058
0.001587
0.017773
0.003338
0.006569
0.018313
0.043116
0.087102
0.152024
0.297131
0.439555
0.841829
1.215208
1.662818
1.684385
0.001132
0.002680
0.003899
0.004481
0.010957
0.015856
0.060138
0.063094
0.128903
0.244296
0.328091
0.640259
0.974273
1.254155
1.421796
0.001549
0.002619
0.003938
0.005441
0.007304
0.026477
0.059101
0.058353
0.118456
0.512465
0.385311
0.272954
0.757982
1.037532
1.042465
0.003259
0.003850
0.002773
0.006798
0.009408
0.024510
0.035822
0.081536
0.109268
0.149115
0.281513
0.549998
0.694487
0.979880
0.925685
Tabelle 8: Matrizenmultiplikation MPI Global
B.2 Messwerte SOM
Kartengröße \ η
0.01
0.02
0.05
0.1
0.2
6
12
24
35
54
70
96
117
150
176
216
247
294
0.979354
0.964106
0.971496
0.967688
0.967150
0.956431
0.968554
0.967589
0.962537
0.961058
0.965004
0.962998
0.957681
0.974925
0.970810
0.970836
0.972489
0.967812
0.964177
0.955066
0.961083
0.967079
0.954644
0.971950
0.961691
0.960076
0.969950
0.968835
0.968242
0.962414
0.968045
0.955921
0.952249
0.963069
0.958687
0.967487
0.955522
0.962436
0.957778
0.959706
0.969341
0.965021
0.965382
0.969016
0.962576
0.967951
0.960837
0.956596
0.961061
0.948122
0.955413
0.960455
0.966196
0.956361
0.954553
0.968662
0.962433
0.959484
0.960326
0.962029
0.961712
0.959043
0.948291
0.956562
0.954079
Tabelle 9: Clusterqualität (seriell) in Abhängigkeit von Lernrate und Kartengröße
- 86 -
Kartengröße \ η
0.01
0.02
0.05
0.1
0.2
6
12
24
35
54
70
96
117
150
176
216
247
294
0.997729
0.989731
0.982539
0.974015
0.962167
0.966213
0.963507
0.952056
0.946226
0.937983
0.939833
0.947016
0.934472
0.991785
0.989421
0.979781
0.984256
0.972806
0.962104
0.972702
0.974855
0.966315
0.953437
0.967333
0.947809
0.938684
1.002955
0.987820
0.986615
0.978814
0.982249
0.977105
0.982685
0.974192
0.982389
0.972333
0.966706
0.977643
0.971364
1.014007
0.989101
0.985965
0.986098
0.984485
0.982592
0.980234
0.980545
0.980699
0.980938
0.983018
0.981370
0.980312
1.002109
0.989269
0.984128
0.983383
0.982571
0.986211
0.981494
0.979453
0.985563
0.981915
0.985436
0.982376
0.982756
Tabelle 10: Clusterqualität (parallel) in Abhängigkeit von Lernrate und Kartengröße
Kartengröße \ Zeit
Real_P
CPU_P
Real_NP
CPU_NP
Real_S
CPU_S
36
72
144
210
324
420
576
702
900
1056
1296
1482
1764
1980
2304
2550
2916
3192
2.09
2.37
2.73
3.07
3.60
4.17
4.91
5.58
6.55
7.62
8.89
9.93
11.64
13.07
14.84
16.43
18.15
19.14
1.08
1.57
2.37
3.01
3.75
4.86
6.22
7.26
9.00
10.78
13.28
15.18
18.04
20.55
23.74
26.26
29.70
31.66
2.89
3.33
4.02
4.76
5.76
6.19
8.25
9.68
11.44
13.13
16.17
18.36
21.28
23.90
30.18
35.67
36.48
40.64
1.24
1.57
2.39
3.07
3.96
4.20
6.24
7.28
9.10
10.72
13.46
15.30
18.25
20.59
24.16
26.68
30.27
32.69
1.68
3.01
4.90
7.30
11.19
14.51
19.63
22.57
29.28
34.44
40.03
47.05
58.95
77.69
93.07
122.93
168.47
182.00
1.48
2.60
4.68
7.02
10.83
14.21
19.22
22.13
28.57
33.70
39.52
46.27
56.03
76.41
88.40
121.10
166.39
178.16
Erläuterung der Spaltennamen: Real=Verstrichene reale Gesamtzeit; CPU=Verstrichene CPUZeit; P=Paralleles Modell auf mehreren Prozessoren; NP=Paralleles Modell auf einem Prozessor; S=Normales Modell auf einem Prozessor
Tabelle 11: Rechenzeit in Abhängigkeit von Kartengröße und Parallelisierungsgrad
- 87 -
Größe \ #
12
24
35
54
70
96
117
150
176
1
2
.41
1.54
3.35
8.11
13.73
26.28
46.16
81.27
111.42
3
.65
1.22
2.24
4.83
7.97
14.77
22.23
37.49
60.10
4
5
6
.90
1.34 1.63 1.94
1.22 1.89 2.18 2.54
2.08 1.87 3.00 3.47
3.96 3.47 4.86 5.12
6.47 5.93 5.09 6.23
10.74 8.53 9.23 9.10
16.02 14.10 11.26 10.36
26.24 21.15 17.11 20.95
36.11 27.94 24.22 20.56
7
8
9
10
11
12
2.44
3.30
3.57
5.68
8.54
10.63
14.84
19.65
24.64
2.92
3.38
4.25
5.41
7.19
10.10
14.10
17.11
27.03
3.41
4.17
4.73
5.96
7.77
11.05
13.03
19.46
21.98
3.61
4.37
4.75
5.94
7.96
10.07
12.22
17.93
24.75
4.34
4.81
5.38
6.07
7.47
10.00
12.19
17.46
23.16
4.30
4.85
5.76
7.41
9.50
12.29
12.49
16.69
21.96
Tabelle 12: Berechnungszeiten Parallele SOM mit OpenMP
B.3 Messwerte Feed-Forward-Netz
Tabelle 13: Erkennungsrate Teilklassifikatoren
Klassifikator
Zyklen
Korrekt (%)
binär #0
binär #1
binär #2
binär #3
binär #4
binär #5
binär #6
binär #7
binär #8
binär #9
binär #10
binär #11
binär #12
binär #13
binär #14
binär #15
binär #16
binär #17
binär #18
binär #19
27
27
26
37
28
24
22
63
236
57
21
22
27
22
22
21
19
30
28
24
Min.:
99,80
99,90
100,00
99,70
100,00
99,90
99,80
99,30
98,50
100,00
100,00
99,80
100,00
100,00
100,00
100,00
100,00
100,00
98,70
99,70
98,50
- 88 -
Tabelle 14: Rechenzeiten und Erkennungsrate n-Fach vs. binär nach Netzgröße
#Hidden
Zyklen
Treal
1
1000
2
3
4
5
6
7
8
9
1000
1000
1000
1000
261
212
155
115
109
3,8250
4,6560
5,5290
6,3300
6,8410
2,2980
2,1230
1,9060
1,3940
1,5010
1,1890
1,4020
1,2260
1,3480
1,3340
10
11
12
13
14
15
76
89
76
66
68
n-Fach
Tcpu
3,6700
4,5200
5,4300
6,1800
6,7200
2,1900
2,0400
1,7500
1,2800
1,4000
1,0700
1,2800
1,1100
1,2500
1,2200
Korrekt %
Treal
binär
Tcpu
23,50
59,30
90,10
97,90
99,60
100,00
100,00
99,90
99,90
100,00
100,00
100,00
100,00
100,00
100,00
1,8310
0,9170
0,9110
0,9460
1,0140
1,0730
1,1750
1,0440
1,2420
1,1440
1,0640
1,1420
1,1620
1,0620
1,0530
4,1900
2,2600
2,5100
2,7000
2,8300
3,0200
2,9700
3,1000
3,9700
3,2700
3,3300
3,3400
3,6800
4,1900
3,9800
Korrekt %
98,50
99,00
99,00
99,40
98,90
99,10
99,40
98,90
98,80
98,80
99,00
98,90
99,00
98,90
98,30
B.4 Inhalt der CD
Die in dieser Arbeit vorgestellten Programme und Algorithmen sind auf der beiliegenden CD
enthalten, ebenso die verwendeten Skripte zum Erzeugen der Messdaten. Jedes Verzeichnis enthält eine README-Datei mit Informationen über den Inhalt des Verzeichnisses sowie dessen
Verwendung.
/sxml SXML Umgebung.
/matrix Testprogramme zur Matrizenmultiplikation
/cache Testprogramm Row-First vs. Column-First-Ordering
/nn_parallel Parallele Selbstorganisierende Karte
/snns_ff n-Fach-Klassifikator vs. Binäre-Klassifikatoren
/snns_som Hierarchischen Kohonen-Karte
/quellen Ergebnistabelle des Linpack-Benchmark (http://www.top500.org/lists/2008/06)
- 89 -