Moderne Expression Templates Programmierung ... · Moderne Expression Templates Programmierung –...
Transcript of Moderne Expression Templates Programmierung ... · Moderne Expression Templates Programmierung –...
Moderne Expression Templates Programmierung
–
Weiterentwickelte Techniken und
deren Einsatz zur Losung partieller
Differentialgleichungen
Der Technischen Fakultat der
Universitat Erlangen-Nurnberg
zur Erlangung des Grades
DOKTOR–INGENIEUR
vorgelegt von
Dipl.-Math. Jochen Hardtlein
Erlangen — 2007
Als Dissertation genehmigt von
der Technischen Fakultat der
Universitat Erlangen-Nurnberg
Tag der Einreichung 20. Marz 2007
Tag der Promotion: 03. August 2007
Dekan: Prof. Dr. A. Leipertz
Berichterstatter: Prof. Dr. C. Pflaum
Prof. Dr. J. Wolff von Gudenberg
DANKSAGUNG
Danksagung
Mein besonderer Dank richtet sich an meinen Betreuer Prof. Dr. Christoph Pflaum (Lehrstuhl Infor-
matik 10, Universitat Erlangen-Nurnberg). Im Rahmen des vorgegebenen Themengebiets gab er mir
die Moglichkeiten meine eigenen Ideen und Forschungsinteressen umzusetzen. Dabei bot er mir immer
hervorragende wissenschaftliche Hilfe und zielgerichtete Unterstutzung an, die mir oft neue Sichtweisen
und Losungsansatze eroffneten. Ich bedanke mich ganz herzlich bei Dir fur die stets freundliche Betreu-
ung, die Ehrlichkeit, alles Lob und jede Kritik! Des Weiteren mochte ich auch Prof. Dr. Jurgen Wolff
von Gudenberg (Lehrstuhl Informatik 2, Universitat Wurzburg) fur die Begutachtung dieser Arbeit
Dank sagen.
Ich bedanke mich bei der Deutschen Forschungsgemeinschaft (DFG) fur die Finanzierung meiner Stel-
le im Rahmen des DFG-Projekts Expression Templates fur partielle Differentialgleichungen (Geschaftz.
PF 372/3-1, PF 372/3-2). Dies ermoglichte mir die umfangreiche und konzentrierte Forschungsarbeit,
die zu dieser Dissertation fuhrte.
Ein herzliches Dankeschon allen derzeitigen und ehemaligen Mitarbeitern am LSS fur die freund-
schaftliche und hilfsbereite Arbeitsatmosphare in den letzten Jahren, besonders an meine Zimmergenos-
sen: Britta Heubeck, Tobias Gradl, Matthias Hummer und Markus Kowarschik. Die Zusammenarbeit
sowie die vielen fachlichen und personlichen Gesprache habe ich wirklich sehr genossen! Bei Proble-
men jeder Art bin ich auf offene Ohren und nutzliche Ratschlage gestoßen. Zusatzlich mochte ich all
diejenigen besonders herausheben, die dankenswerterweise Teile dieser Arbeit korrekturgelesen haben.
Zuletzt gilt mein tiefer Dank meiner Freundin, meiner Familie und meinen Freunden. Danke fur Eure
Geduld und die unschatzbare Unterstutzung, besonders Dir, liebe Merle!
Jochen Hardtlein
iii
DANKSAGUNG
iv
INHALTSVERZEICHNIS
Inhaltsverzeichnis
Abbildungs- und Tabellenverzeichnis ix
Verzeichnis der Listings xii
1 Einleitung 1
1.1 Inhaltliche Ausrichtung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.2 Motivation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.2.1 Entwicklung numerischer Software . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.2.2 Effiziente Implementierungen via ET . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.2.3 ET zur Losung partieller Differentialgleichungen . . . . . . . . . . . . . . . . . . 3
1.2.4 Weiterfuhrende ET-Techniken und Anwendungen . . . . . . . . . . . . . . . . . . 3
1.3 Uberblick . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
I ET – Grundlagen, Entstehung und Analyse 5
2 Sprachgrundlagen und Programmierkonzepte 7
2.1 Domain-specific Embedded Languages . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
2.2 Benutzerfreundliche, mathematische Schnittstellen . . . . . . . . . . . . . . . . . . . . . 7
2.2.1 Uberladen von Operatoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
2.2.2 Benutzerdefinierte Konvertierungsoperatoren . . . . . . . . . . . . . . . . . . . . 9
2.3 Templates in C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
2.3.1 Definition von templatisierten Klassen und Funktionen . . . . . . . . . . . . . . . 10
2.3.2 Template-Instantiierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
2.3.3 Meta-Template-Programmierung . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
2.3.4 Traits . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
2.3.5 Templategesteuerte Vererbung . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
2.3.6 CRTP (Curiously Recurring Template Pattern) . . . . . . . . . . . . . . . . . . . 13
2.4 Optimierungen von C++-Compilern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
2.4.1 Elimination temporarer Objekte . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
2.4.2 Inlining . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
2.4.3 Aliasing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
2.4.4 Optimierung von Schleifen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
2.4.5 Kai C++-Compiler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
2.5 Zugrundeliegende Programmcodes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
3 Klassische ET 19
3.1 Das traditionelle Uberladen von Operatoren . . . . . . . . . . . . . . . . . . . . . . . . . 19
3.1.1 Unmittelbare Auswertung im Operator . . . . . . . . . . . . . . . . . . . . . . . 19
3.1.2 Ausdrucksbaume mittels abstrakter Klassen . . . . . . . . . . . . . . . . . . . . . 20
3.2 Klassische ET-Implementierungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
3.2.1 ET-Programmierung via Veldhuizen . . . . . . . . . . . . . . . . . . . . . . . . . 22
3.2.2 Verbreitete ET-Implementierungen . . . . . . . . . . . . . . . . . . . . . . . . . . 22
v
INHALTSVERZEICHNIS
3.3 Bekannte ET Bibliotheken . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
4 Analyse von ET und Verbesserungsansatze 27
4.1 Effizienz der ET-Technik . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
4.2 Komponenten von ET . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
4.2.1 Wrapper-Klassen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
4.2.2 Grunddatenstrukturen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
4.2.3 Operationsklassen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
4.2.4 Creator Funktionen und Uberladene Operatoren . . . . . . . . . . . . . . . . . . 34
4.3 Lebensdauer von ET-Objekten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
II Weiterfuhrende Programmierung von Expression Templates 37
5 Einfache ET-Implementierung 39
5.1 Komplexitat der Implementierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
5.2 Namespace-gekapselte ET-Implementierungen . . . . . . . . . . . . . . . . . . . . . . . . 39
5.3 Einfache, typsichere ET . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
5.4 Einfache ET fur temporare Ausdrucke . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
6 Fast Expression Templates 45
6.1 ET im Hochleistungsrechnen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
6.1.1 Aliasing-Probleme bei ET . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
6.2 Fast Expression Templates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
6.2.1 Template-Nummerierung der Vektorklassen . . . . . . . . . . . . . . . . . . . . . 46
6.2.2 Meta-FET . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
6.3 Leistungsanalyse der FET-Implementierungen . . . . . . . . . . . . . . . . . . . . . . . . 49
6.3.1 Allgemeine Bemerkungen zur Analyse . . . . . . . . . . . . . . . . . . . . . . . . 49
6.3.2 Effizienz auf Workstations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
6.3.3 FET auf Hochleistungsrechnern . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
6.4 Praktische Anwendung von FET . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
6.4.1 Einschrankung auf rechenintensive Programmteile . . . . . . . . . . . . . . . . . 53
6.4.2 Automatisches Nummerieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
7 Erweiterte Methoden 57
7.1 Typ-Minimierung durch Traits . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
7.2 ET fur verschiedene Datenstrukturen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
7.3 Speicherung von ET . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
7.3.1 Typkapselung und Formeln-Schablonen . . . . . . . . . . . . . . . . . . . . . . . 60
7.3.2 Abspeicherung mittels abstrakter Klassen . . . . . . . . . . . . . . . . . . . . . . 61
7.4 Sichere Matrix-Multiplikationen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
8 Kompilierzeiten von ET 65
8.1 Ubersetzung von ET-Programmen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
8.2 Praktische Methoden zur schnelleren Compilierung . . . . . . . . . . . . . . . . . . . . . 66
8.2.1 Template-Anordnung in den Operationsklassen . . . . . . . . . . . . . . . . . . . 67
8.2.2 Balancierung der Ausdrucksbaume . . . . . . . . . . . . . . . . . . . . . . . . . . 68
8.2.3 Auslagerung von ET-Ausdrucken . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
vi
INHALTSVERZEICHNIS
9 ET – Ausblick 71
9.1 Limitierungen von ET-Bibliotheken . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
9.1.1 Komplexe Fehlermeldungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
9.1.2 Testbarkeit von ET-Programmen . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
9.1.3 Grenzen der Optimierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
9.2 Erweiterte Anwendungsgebiete . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
9.3 Abschließende Bemerkungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
III Einsatz von ET zum Losen von partiellen Differentialgleichungen 75
10 Finite Elemente 77
10.1 Die Finite Elemente Methode . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
10.1.1 Die schwache Formulierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
10.1.2 Gebietszerlegungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
10.1.3 Finite Elemente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
10.1.4 Referenzelemente und Transformationen . . . . . . . . . . . . . . . . . . . . . . . 81
10.1.5 Numerische Quadratur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
10.2 Einige Finite Elemente Approximationen . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
10.2.1 Konforme Finite Elemente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
10.2.2 Nichtkonforme Finite Elemente . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
10.2.3 Gemischte FEM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
10.2.4 Vektorwertige Finite Elemente . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
10.3 FE-Software . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
11 Colsamm 89
11.1 Grundidee und Programmstruktur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
11.1.1 Programmablauf . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
11.2 Assemblierung von Diskretisierungsmatrizen . . . . . . . . . . . . . . . . . . . . . . . . . 91
11.3 Integranden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
11.3.1 Platzhalter fur Basisfunktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
11.3.2 Differentialoperatoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
11.3.3 Konstanten, Polynome, Vektoren . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
11.3.4 Benutzerdefinierte, veranderliche Funktionen . . . . . . . . . . . . . . . . . . . . 95
11.4 Definition neuer Finiter Elemente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
11.4.1 Transformationen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
11.4.2 Basisfunktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
11.5 Anwendungsbeispiele . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
11.5.1 Finite Elemente im optischen Fluss . . . . . . . . . . . . . . . . . . . . . . . . . . 98
11.5.2 Quell-Lokalisation in Dipolmodellen . . . . . . . . . . . . . . . . . . . . . . . . . 100
11.6 Entwicklung und Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
11.6.1 Schnelle Berechnungen der lokalen Steifigkeitsmatrizen . . . . . . . . . . . . . . . 103
11.6.2 Erweiterungen und Ausblick . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
12 Weitere Einsatzgebiete von ET 107
12.1 Mengenalgebren mittels ET . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
12.2 Iterationsschleifen mittels ET . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109
12.3 ET-Schleifenblocking mit nummerierten Variablen . . . . . . . . . . . . . . . . . . . . . 114
vii
INHALTSVERZEICHNIS
13 Abschließende Bemerkungen 117
13.1 Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117
13.1.1 Einfache und Leistungsstarke ET-Implementierungen . . . . . . . . . . . . . . . . 117
13.1.2 ET zum Losen von partiellen Differentialgleichungen . . . . . . . . . . . . . . . . 117
13.2 Weitere Entwicklungen und Ausblick . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118
IV Anhang 121
A ET-Implementierung nach Veldhuizen 123
B Colsamm – Programmstruktur 127
B.1 Template-Bibliotheken . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127
B.2 Programmstruktur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127
B.2.1 Domain . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128
B.2.2 Gaussian Points . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131
B.2.3 Basis Function . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131
B.2.4 Mapping . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131
B.2.5 CornerClasses . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132
B.2.6 StencilTypeInit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132
B.2.7 Simple Element, Mixed Element, Vectorial Element . . . . . . . . . . . . . . . . . . . . . . 132
C Programmierung von Typlisten 135
D Verwendete Computer-Plattformen 139
D.1 Arbeitsplatzrechner . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139
D.1.1 Pentium 4 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139
D.1.2 Dual-Core . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139
D.2 Hochleistungsrechner . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139
D.2.1 NEC SX-6/48M6 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139
D.2.2 Hitachi SR8000-F1/168 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140
D.2.3 Cluster . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140
E Curriculum Vitae 141
Eigene Veroffentlichungen 143
Literaturverzeichnis 145
viii
ABBILDUNGS- UND TABELLENVERZEICHNIS
Abbildungs- und Tabellenverzeichnis
4.1 Graphische Darstellung des ET-Ausdrucksbaums der Vektortriade. . . . . . . . . . . . . 29
6.1 Performance-Analyse der FET-Implementierungen auf Workstations. . . . . . . . . . . . 50
6.2 Laufzeitleistung von FET-Programmen auf der NEC-SX6. . . . . . . . . . . . . . . . . . 51
6.3 Untersuchung praxisbezogener FET-Anwendungen auf NEC-SX6. . . . . . . . . . . . . . 52
6.4 Performance-Vergleiche von FET-Bibliotheken unter Verwendung von OpenMP. . . . . . 53
7.1 Typkombinationen von reell- bzw. komplexwertigen Vektoren . . . . . . . . . . . . . . . 57
7.2 Untersuchung verschiedener Implementierungen zur Speicherung von ET-Objekten. . . . 63
8.1 Ubersetzungszeiten verschiedener ET-Implementierungen. . . . . . . . . . . . . . . . . . 66
10.1 Transformation des Referenzelements auf allgemeine Dreiecke. . . . . . . . . . . . . . . . 82
10.2 Abbildung einer Richtung im Referenzelement in bel. Elemente. . . . . . . . . . . . . . . 83
10.3 Transformation des Referenzelement auf isoparametrische Dreiecke mit einer krummli-
nigen Kante. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
10.4 Kantenelemente fur Dreiecke und Tetraeder. . . . . . . . . . . . . . . . . . . . . . . . . . 86
11.1 Strukturelle Darstellung der numerischen Losung von PDGen mit der Finiten Elemente
Methode unter Verwendung von Colsamm. . . . . . . . . . . . . . . . . . . . . . . . . . . 90
11.2 Programmablauf von Colsamm zur Berechnung der lokalen Diskretisierungsmatrizen. . . 91
11.3 Frames 8 und 9 der bekannten Yosemite-Sequenz. . . . . . . . . . . . . . . . . . . . . . . 99
11.4 Optisches Flussfeld fur Frame 8 und 9 der Yosemite-Sequenz. . . . . . . . . . . . . . . . 100
11.5 Visualisierung der Norm des berechneten Geschwindigkeitsfeld einer Sequenz von Herz-
bewegungen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
11.6 Colsamm-Anwendung zur Berechnung von Dipolquellen in realistischen Kopfmodellen. . 103
12.1 Performance-Analyse des ET-Schleifen-Blockings. . . . . . . . . . . . . . . . . . . . . . . 116
B.1 Klassenhierarchie von Colsamm. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128
ix
ABBILDUNGS- UND TABELLENVERZEICHNIS
x
VERZEICHNIS DER LISTINGS
Verzeichnis der Listings
2.1 Vektordatenstruktur, Code-Grundlage fur weitere Implementierungen. . . . . . . . . . . 18
3.1 Implementierung eines Ausdrucksbaumes mittels abstrakter Klassen. . . . . . . . . . . . 21
3.2 Gangige Implementierungsvariante der ET-Technik. . . . . . . . . . . . . . . . . . . . . 23
3.3 Variante eines uberladenen Operators ohne explizite Wrapper-Klasse. . . . . . . . . . . . 24
4.1 Methode zum Abspeichern der Operanden ohne Wrapper. . . . . . . . . . . . . . . . . . 29
4.2 Wrapper-Klasse mit explizitem Konvertierungsoperator. . . . . . . . . . . . . . . . . . . 30
4.3 ET-Programmiervariante mit allgemeinen Operationsklassen. . . . . . . . . . . . . . . . 33
4.4 ET-Programmcode mit separaten Operationsklassen. . . . . . . . . . . . . . . . . . . . . 34
4.5 Ableitung der Operationsklassen vom Wrapper mittels CRTP. . . . . . . . . . . . . . . . 34
4.6 Vektor-Exponentialfunktion mit Creator-Funktion. . . . . . . . . . . . . . . . . . . . . . 34
5.1 Namespace-gekapselte ET-Implementierung. . . . . . . . . . . . . . . . . . . . . . . . . . 40
5.2 Programm-Code einer einfachen ET-Variante. . . . . . . . . . . . . . . . . . . . . . . . . 42
5.3 ET-Implementierung zur Speicherung temporarer Operanden. . . . . . . . . . . . . . . . 43
6.1 Template-nummerierte Vektordatenstruktur. . . . . . . . . . . . . . . . . . . . . . . . . . 47
6.2 Meta-FET-Implementierung ohne Abspeicherung der Operanden. . . . . . . . . . . . . . 48
6.3 Nummerierbare Klasse zur FET-Kapselung von Skalaren. . . . . . . . . . . . . . . . . . 48
6.4 Template-nummerierte Vektordatenstruktur zur eingeschrankten Verwendung in rechen-
intensiven Programmteilen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
6.5 Praktische Anwendung der FET-Implementierungen an rechenintensiven Stellen. . . . . 54
7.1 Traits fur die Bestimmung des Ruckgabetyps. . . . . . . . . . . . . . . . . . . . . . . . . 57
7.2 ET-Implementierung fur ubersetzergenerierte Ruckgabetypen. . . . . . . . . . . . . . . . 58
7.3 ET fur verzogerte Auswertungen der Komponentenoperationen. . . . . . . . . . . . . . . 59
7.4 Programmiervariante zum Behandeln mehrerer Datenstrukturen und Laufzeitfehlermel-
dungen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
7.5 Speicherung von ET als Member-Variablen mit vollem Datentyp. . . . . . . . . . . . . . 61
7.6 Anwendung von gespeicherten FET-Ausdrucken. . . . . . . . . . . . . . . . . . . . . . . 61
7.7 Abspeicherung von ET-Objekten mittels abstrakter Klassen. . . . . . . . . . . . . . . . 62
7.8 Abspeicherung mit abstrakten Klassen und mehreren Ergebnistypen. . . . . . . . . . . . 63
8.1 Programmcodes mit vertauschten Template-Parametern. . . . . . . . . . . . . . . . . . . 68
8.2 Einfache, schneller ubersetzbare ET-Implementierung. . . . . . . . . . . . . . . . . . . . 68
11.1 Beispiel-Programm zur Anwendung von Colsamm . . . . . . . . . . . . . . . . . . . . . . 93
11.2 Implementierung der Tetraeder-Transformationsformel. . . . . . . . . . . . . . . . . . . . 96
11.3 Transformationsformel fur FE-Dreiecke mit einer krummlinigen Seite. . . . . . . . . . . 96
11.4 Definition eines Tetraederelements in Colsamm. . . . . . . . . . . . . . . . . . . . . . . . 96
11.5 Definition von Nedelec-Elementen erster Ordnung in Colsamm. . . . . . . . . . . . . . . 97
12.1 ET-Implementierung der Test- und Verknupfungsklassen zur Realisierung einer Men-
genalgebra. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
12.2 Verwendung der ET-Mengenalgebra im Anwendercode. . . . . . . . . . . . . . . . . . . . 108
12.3 Datenstruktur fur die verzogerte Auswertung in Iterationsschleifen. . . . . . . . . . . . . 109
12.4 Zusweisungsoperator zur Durchfuhrung der verzogerten Auswertung. . . . . . . . . . . . 109
12.5 Wrapper-Klasse fur Index-Ausdrucke . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
12.6 ET-Programmierung der Schleifeninitialisierung. . . . . . . . . . . . . . . . . . . . . . . 110
xi
VERZEICHNIS DER LISTINGS
12.7 ET-Code zur Durchfuhrung von Bedingungsabfragen. . . . . . . . . . . . . . . . . . . . 110
12.8 ET-Implementierung fur die Durchfuhrung von Iterationsschritten. . . . . . . . . . . . . 111
12.9 Index-Klasse fur die Schleifeniteration. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111
12.10Wrapper-Klasse der ET-Iterationsschleifen. . . . . . . . . . . . . . . . . . . . . . . . . . 112
12.11 ET-Iterationsschleife zur Durchfuhrung einfacher Iterationen. . . . . . . . . . . . . . . . 112
12.12 Anwendungsbeispiel der ET-Schleifen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
12.13 ET-Programmierung von geschachtelten Schleifen. . . . . . . . . . . . . . . . . . . . . . 113
12.14 ET-Klasse zur Kapselung der inneren Schleifen. . . . . . . . . . . . . . . . . . . . . . . 113
12.15 Geschachtelte Anwendung der ET-Schleifen. . . . . . . . . . . . . . . . . . . . . . . . . 114
12.16 Typlist-Definition in der Additionsklasse. . . . . . . . . . . . . . . . . . . . . . . . . . . 114
12.17 Erweiterung der Vektorklasse zur Durchfuhrung des Blockings. . . . . . . . . . . . . . . 115
12.18 Erweiterung der Evaluate-Klasse fur Schleifenblocking. . . . . . . . . . . . . . . . . . . . 115
12.19 ET-Iterationsschleife zur Durchfuhrung des Blockings. . . . . . . . . . . . . . . . . . . . 115
12.20 Anwendercode des geblockten Gauß-Seidel-Losers. . . . . . . . . . . . . . . . . . . . . . 116
A.1 Vektor-Klasse der ET-Implementierung nach Veldhuizen. . . . . . . . . . . . . . . . . . . 123
A.2 Funktion zur Auswertung und Zuweisung von ET-Objekten. . . . . . . . . . . . . . . . . 124
A.3 Operationsklassen und Operatoren der ursprunglichen ET-Variante . . . . . . . . . . . . 125
B.1 Liste der Template-Parameter der Klasse Domain . . . . . . . . . . . . . . . . . . . . . . 128
xii
ZUSAMMENFASSUNG
Zusammenfassung
Die Implementierungstechnik der Expression Templates (ET) stellt in C++ eine machtige und inter-
essante Methode dar, das benutzerfreundliche Uberladen von Operatoren mit einer effizienten Program-
mierung zu verbinden. Dabei werden mathematische Ausdrucke mittels der entsprechenden uberlade-
nen Operatoren in Template-Operationsklassen gekapselt, die hochoptimierte Berechnungen der Ergeb-
nisse in einem Auswertungsschritt erlauben. Darauf aufbauend wurden einige sehr leistungsfahige und
intuitiv anwendbare Bibliotheken, vorwiegend fur Problemstellungen der linearen Algebra, entwickelt.
Daruber hinaus sind aber auch Programmpakete zur Losung partieller Differentialgleichungen entstan-
den. Trotz der leistungsfahigen Programmierung von mathematischen Operatoren mittels ET und der
dadurch eroffneten Moglichkeit Bibliotheken mit effizienten und benutzerfreundlichen Schnittstellen zu
implementieren, fand die ET-Technik nur zogerlich Anwendung. Dies liegt einerseits an der teilweise
langwierigen und fehleranfalligen Implementierung, sowie andererseits daran, dass die ET-Programme
nicht immer die erwartete Performance erreichen. Dies zeigt sich besonders auf Hoch- und Hochst-
leistungsrechnern, wodurch die Anwendung von ET auf diesen Plattformen bis jetzt wenig Beachtung
fand.
Die vorliegende Arbeit prasentiert grundsatzliche Weiterentwicklungen der ET-Technik sowie deren
Anwendungen in Template-Bibliotheken. Dabei werden nach eingehender Analyse bestehender ET-Im-
plementierungen einfach zu realisierende ET-Varianten (EET) entwickelt, die sehr eingangig auch fur
darin ungeubte Programmierer anwendbar sind. Daruber hinaus werden durch die Einfuhrung von Fast
Expression Templates (FET) die bestehenden Effizienzmangel der herkommlichen ET-Programmierung
behoben. Diese weiterentwickelte Implementierungstechnik unterstutzt durch ihre Programmierung den
Compiler beim Optimieren der geschachtelten Operationsobjekte. Dadurch ergeben sich mittels FET-
Bibliotheken leistungsfahige Programme, die auch auf Hoch- bzw. Hochstleistungsrechnern die Effizienz
von Implementierungen in C erreichen. Neben der Einfuhrung in einige weiterfuhrenden Techniken
werden zusatzlich die Ubersetzungszeiten von ET-Bibliotheken untersucht und beschleunigt.
Nach den grundlegenden Weiterentwicklungen von ET werden generelle Anwendungen der Tech-
nik zur Losung von partiellen Differentialgleichungen betrachtet. Insbesondere wird eine Template-
Bibliothek diskutiert, welche zur flexiblen, effizienten und intuitiven Berechnung lokaler Diskretisie-
rungssterne dient, die aus Finiten Element Ansatzen entstehen. Dabei werden die Implementierungs-
strategien und der Wirkungsumfang detailliert beschrieben. Zusatzlich werden einige aktuelle Problem-
stellungen dargestellt, in denen die Bibliothek Anwendung findet.
Die im Rahmen vorliegender Arbeit prasentierten Implementierungstechniken und deren Anwendun-
gen erleichtern die problemspezifische Entwicklung effizienter und benutzerfreundlicher Bibliotheken in
C++.
xiii
ZUSAMMENFASSUNG
xiv
ABSTRACT
Abstract
The Expression Templates (ET) implementation technique represents a powerful method for accom-
plishing efficient operator overloading in C++. Hence, the mathematical expressions are encapsulated
in template operation classes which allow for highly optimized evaluations. Based on the ET tech-
nique several powerful libraries were implemented, mainly for solving linear algebraic problems, and
moreover, programs for solving partial differential equations. Even if ET yield high efficient and easy
usable programs many scientific programmers hesitated to implement this technique. This was mainly
caused by the fault-prone and laborious coding of such problem-specific libraries. Furthermore, ET
implementations on high performance platforms still do not reach the expected efficiency.
In the scope of this thesis several basic enhancements are presented. Firstly, easy implementable
ET versions are discussed which can also be applied by programmers having low experience with
this technique. However, the presented versions of ET implementations require – in difference to the
common ET variants – only one overloaded version of each operator. As a consequence, the coding of
such libraries is faster and easier. The remaining performance lacks of common ET implementations
are focused by the introduction of Fast Expression Templates (FET). This advanced programming
technique supports the compiler in resolving nested template types. Hence, those FET-based libraries
achieve an efficiency of corresponding C-codes even on high performance platforms. In addition to the
FET technique, this thesis outlines some continuative techniques for a more general usage, as well as
the study of the compile times resulting from ET implementations.
After these fundamental enhancements of the ET programming technique we focus on the general
applications of the resulting methods for the numerical solution of partial differential equations. In
particular, a template library is discussed, that provides flexible, efficient and intuitive computation of
local stiffness matrices which arise from finite element discretizations. In addition, the implementation
policies and the features realized are described. Several application examples of the template library
are summarized.
The presented implementation techniques and their applications ease the problem-specific develop-
ment of efficient and user-friendly C++-libraries.
xv
ABSTRACT
xvi
KAPITEL 1. EINLEITUNG
1 Einleitung
1.1 Inhaltliche Ausrichtung
Die Ergebnisse der vorliegenden Arbeit dienen als Beitrag zu der einfachen Programmierung effizienter
und benutzerfreundlicher mathematischer Bibliotheken in C++. Der Fokus liegt dabei besonders auf
der Vereinfachung und Verbesserung von Implementierungstechniken, sowie deren Anwendungen in
praxisrelevanten Problemstellungen des wissenschaftlichen Rechnens. Im Rahmen der Weiterentwick-
lung und des erweiterten Einsatzes der Expression Templates (ET) Programmiertechnik werden die
folgenden Themengebiete behandelt:
• Analyse bestehender ET-Bibliotheken und Vereinfachung der ET-Programmierung, unter Ver-
meidung zusatzlicher Fehlerquellen bei der praktischen Realisierung
• Entwicklung und Untersuchung leistungsfahiger Implementierungsvarianten von ET fur eine ver-
lustfreie Verwendung auf Hoch- und Hochstleistungsrechnern
• Einsatz von ET zur Losung partieller Differentialgleichungen mit einer Detail-Studie an einer
Template-Bibliothek zur Berechnung lokaler Diskretisierungsmatrizen
1.2 Motivation
Die Erarbeitung realistischer und damit immer großerer und komplexer Simulationen praxisrelevanter
Problemstellungen stellt die Software-Entwickler in Industrie und Forschung vor standig neue Heraus-
forderungen. Parallel zu den wachsenden Aufgaben ist auch eine steigende Kapazitat der Rechenleis-
tung zu verzeichnen, die auch stetig kostengunstiger zu haben ist. Dies erfordert von Programmierern
sich mit immer komplexeren Losungsansatzen auseinander zu setzen, welche auch zu komplizierteren
und umfangreicheren Computerprogrammen fuhren. Beispielsweise umfassen aktuelle praxisbezogene
numerische Simulationen oft mehr als einer Million Zeilen an Programmcode. Des Weiteren sind diese
Anwendungen stetig zu warten bzw. zu erweitern.
1.2.1 Entwicklung numerischer Software
Aus Sicht des Entwicklers stellen umfangreiche numerische Anwendungen einen enormen implemen-
tierungstechnischen sowie programmierungsorganisatorischen Aufwand dar. Gerade in den Program-
miersprachen FORTRAN und C, in denen kaum sprachtechnische Mittel zur Verfugung stehen, um
eine Kapselung der Daten vorzunehmen, treten leicht Probleme mit der passenden Indizierung kom-
plizierter arraybasierter Datenstrukturen auf. Implementierungen mittels Zugriffsfunktionen oder auch
Makros, welche bereits eine gewisse Programmiersicherheit bieten, fuhren meist zu schwer lesbaren
Codes. Daruber hinaus ist eine strukturierte, modulare und dadurch ubersichtliche Programmierung
in FORTRAN oder C nur mit viel Programmierdisziplin moglich. Dabei sind gerade die Lesbarkeit und
eine gewisse Ubersichtlichkeit der Programme wichtig, wenn Wartungen, Anderungen oder Erweiterun-
gen der Software durchgefuhrt werden sollen. Vor diesem Hintergrund erfolgte eine Weiterentwicklung
der Programmiersprache C, die gerade fur die Implementierung großer Projekte geeignete Mittel bieten
sollte.
1
1.2. MOTIVATION
Die Programmiersprache C++ fuhrte zur Unterstutzung der modularen Entwicklung von Software
objektorientierte Programmierparadigmen ein, welche die Definition von benutzereigenen Datentypen
sowie Datenkapselung und Vererbung ermoglichen [Str00]. Neben diesen neuen Mechanismen zum
strukturierten Erstellen von Programmen wurde C++ unter anderem durch eine strengere Typprufung,
dem Template-Mechanismus, dem Uberladen von Operatoren und dem Error-Handling erweitert. Trotz
der neuen Programmierparadigmen liefert C++ als Weiterentwicklung von C aber auch vergleichbar
leistungsstarke Programme, da die handwarenahe Programmierung wie in C weiterhin moglich ist.
Gerade die erreichbare Performance macht C++ fur numerische Problemstellungen interessanter als
andere Sprachen mit vergleichbaren Programmierparadigmen (z.B. Java).
Durch diese Verbesserung der Programmstrukturierung wurde C++ nach der Einfuhrung fur viele
komplexe Anwendungen herangezogen, so zum Beispiel fur Betriebssysteme, Datenbanken oder auch
zur Berechnung numerisch-mathematischer Problemstellungen. Gerade fur letztgenannte bietet C++
mit Templates und dem Operatoruberladen sehr geeignete Hilfsmittel, um die Programmierung und
Benutzung entsprechender Bibliotheken zu erleichtern. Mittels Templates werden Klassen mit Typen als
Parameter ausgestattet, so dass deren Grunddatentypen durch den Anwender festgelegt werden konnen.
Damit wurden die abstrakten Datentypen der Standard Template Library (STL) implementiert, die
direkt in anderen Codes verwendet werden konnen, um die Programmentwicklung zu beschleunigen.
Das Uberladen von Operatoren hingegen ermoglicht es intuitive Schnittstellen zu schaffen, die sehr nahe
an den mathematischen Formulierungen liegen, wodurch einfacher anwendbare Bibliotheken entstehen.
Außerdem wird der Code durch die in-order Schreibweise viel ubersichtlicher und lesbarer, wodurch
Fehler bei der Anwendung verringert werden.
Doch gerade fur eine effiziente Implementierung benutzerfreundlicher Schnittstellen in C++ fehlten
zunachst geeignete Konzepte, so dass leicht und intuitiv anwendbare Bibliotheken oft hinter der erwar-
teten Performance zuruckblieben. Speziell auf Hochleistungsrechnern ist in der Regel eine hardwarenahe
Programmierung, die kaum auf Benutzerfreundlichkeit ausgerichtet ist, notig, um die plattformspezifi-
schen Details ausnutzen zu konnen. Durch die weit gefassten Spracheigenschaften sind gewisse Optimie-
rungen in C++ nur sehr konservativ bzw. schwieriger als z.B. in FORTRAN durchzufuhren. Als Folge
wurde der Implementierung benutzerfreundlicher Software besonders im High-Performance-Computing
wenig Aufmerksamkeit zuteil, da die Effizienz der rechenaufwandigen Programme fur vorwiegend er-
gebnisorientierte Entwickler im Vordergrund steht.
1.2.2 Effiziente Implementierungen via ET
Durch die Einfuhrung der ET-Technik konnten einige der offensichtlichen Performance-Mangel des tra-
ditionellen Operatoruberladens in C++ behoben werden [Vel95]. Zum einen konnen dadurch die wenig
performanten, von C geerbten, Call-Back-Funktionen umgangen werden, zum anderen ergeben sich
gerade fur vektorbasierte Datenstrukturen Wege zur Vermeidung von lokalen Variablen wahrend der
Auswertung. Mittels ET werden Ausdrucksbaume aufgebaut, die in templatisierten Klassenobjekten
alle Typinformationen der Operationen und Operanden enthalten. Durch die damit durchfuhrbaren
Optimierungen werden besonders fur große Datenstrukturen leistungsstarke Programme erzielt, deren
Performance mit C-Codes vergleichbar ist.
Neben der Entwicklung mehrerer sehr performanter Bibliotheken entfachte sich wieder die Grund-
satzdiskussion, ob C++ mit seinen Programmierparadigmen wirklich mit der Performance von FORT-
RAN oder C konkurrieren kann [Vel97]. Trotz der großen Leistungssteigerung durch die Verwendung
von ET, konnte die Performance von C++-Programmen gerade auf Hochleistungsrechnern oft nicht
oder nur mit einer per Hand angepassten Implementierung erreicht werden.
Die ET-Technik wurde und wird oft nur zogerlich zur Programmierung mathematischer Software
herangezogen. Gerade das Implementieren der gangigen ET-Varianten erscheint zu Beginn sehr kompli-
ziert, und fuhrt bei falscher Benutzung zu sehr langen und oft kryptisch anmutenden Fehlermeldungen.
2
KAPITEL 1. EINLEITUNG
Daruber hinaus fuhren die vielen notigen Versionen von uberladenen Operatoren oft zu langwierigen
Programmcodes, deren Unvollstandigkeit wegen der anwendungsabhangigen Template-Instantiierung
meist erst durch entsprechende Tests aufgedeckt wird.
1.2.3 ET zur Losung partieller Differentialgleichungen
Ausgehend von der ET-Technik wurden einige sehr leistungsstarke und benutzerfreundliche Bibliothe-
ken entwickelt, vorwiegend zur effizienten Realisierung von vektorbasierten, mathematischen Daten-
strukturen und Berechnungen linearer Gleichungen.
Daneben entstanden zum Beispiel mit POOMA [Rum96] oder ExPDE [Pfl01] auch Bibliotheken
zur numerischen Losung von partiellen Differentialgleichungen. Durch die Verwendung von ET konn-
ten dabei auch komplizierte mathematische Ausdrucke wie etwa Differentialoperatoren effizient rea-
lisiert werden, wobei wiederum sehr problemnahe Schnittstellen entstanden. Gerade bei komplexeren
Problemstellungen in drei Dimensionen mit praxisnahen und daher meist unstrukturierten Diskreti-
sierungsgittern, resultieren oft komplizierte Datenstrukturen sowie Diskretisierungen der Operatoren.
Diese sind ohne entsprechende Datenkapselung nur sehr fehleranfallig zu implementieren. Dabei sind
es oft Entwickler aus Anwendungsgebieten wie Natur-, Ingenieur- oder Wirtschaftswissenschaften, die
praxisnahe Aufgabenstellungen losen wollen, jedoch wegen einer weniger fundierten Programmieraus-
bildung Probleme mit der strukturierten Programmentwicklung haben. Gerade fur solche Anwender
sind intuitive und benutzerfreundliche Bibliotheken sehr hilfreich, wobei dabei naturlich nicht auf eine
entsprechende Leistung verzichtet werden soll.
1.2.4 Weiterfuhrende ET-Techniken und Anwendungen
Aus diesen Grunden werden in der vorliegenden Arbeit Methoden zur einfacheren Realisierung von ET-
Bibliotheken prasentiert. Diese verkurzen einerseits die Implementierungen, da jeder Operator nur eine
uberladene Version benotigt. Andererseits ergeben sich auch weniger Verschachtelungen der Templates,
wodurch die Fehlerquellen der ET-Programmierung reduziert werden.
Aufbauend auf dieser einfachen Implementierungsvariante werden die Ursachen analysiert, welche die
Leistung von ET-Anwendungen beeintrachtigen. Die aufgedeckten Optimierungsmangel der betrach-
teten C++-Compiler werden durch eine Template-Nummerierung der Variablen vermieden. Mit dieser
Technik werden die Fast Expression Templates eingefuhrt, die eine rein typbasierte Implementierung
der ET ermoglichen. Dadurch erreichen ET-Bibliotheken insbesondere auch auf Hochleistungsrechnern
die erwartete Performance von Implementierungen im C-Stil.
Im Software-Paket Colsamm werden die werden die einfache, flexible und problembezogene Verwen-
dung der erarbeiteten Techniken demonstriert. Die Template-Bibliothek Colsamm erlaubt die effizien-
te und benutzerfreundliche Berechnung der lokalen Steifigkeitsmatrizen, die aus der Finiten Elemente
Diskretisierung von partiellen Differentialgleichungen entstehen. Durch das effiziente Uberladen von
mathematischen Operatoren ist eine sehr intuitiv einsetzbare sowie leicht erweiterbare Assemblierung
von Diskretisierungsmatrizen moglich, die aus Finiten Elemente Diskretisierungen entstehen.
1.3 Uberblick
Der thematische Rahmen dieser Arbeit gliedert sich in drei Teile: die Prasentation und Analyse be-
stehender ET-Implementierungen, die Weiterentwicklung der ET-Programmiertechnik selbst, und die
problembezogene Anwendung von ET zur Losung von partiellen Differentialgleichungen.
Zunachst werden die grundsatzlichen Untersuchungen und daraus resultierenden Weiterentwicklun-
gen der ET-Technik betrachtet. Dabei werden in Kapitel 2 die programmiertechnischen Grundlagen
in C++, Auszuge aus der Verwendung von Templates, Optimierungsmechanismen von C++-Compi-
lern und die der Arbeit zugrunde liegenden Programmcodes zusammengefasst. Kapitel 3 umfasst die
3
1.3. UBERBLICK
Entwicklung der ET-Technik zur performanteren Realisierung des bis dato fur große Datenstrukturen
ineffizienten Operatoruberladens. Des Weiteren wird die traditionelle Art der Implementierung von
ET in C++ aufgezeigt, sowie gangige Varianten und bekannte ET-Bibliotheken vorgestellt. In Kapi-
tel 4 werden die verschiedenen Bestandteile einer ET-Implementierung analysiert, wobei zunachst die
Performance-Vorteile gegenuber den traditionellen Arten des Uberladens von Operatoren herausgear-
beitet werden. Zusatzlich wird die Lebensdauer von ET-Objekten untersucht.
Aufbauend auf diesen grundlegenden Analysen werden im zweiten Teil beginnend mit Kapitel 5
einfache sowie typsichere Implementierungen der ET-Technik entwickelt. Dazu werden zunachst die
wesentlichen Punkte der Programmiertechnik diskutiert, welche die Programmierung von ET ver-
komplizieren. Danach werden Methoden zur einfacheren Realisierung von ET prasentiert, die eine
typsichere Implementierung ohne unerwartete Mehrdeutigkeiten fur den Compiler ermoglicht. Durch
Herausarbeiten der bestehenden Performance-Probleme der aktuellen ET-Implementierungen wird in
Kapitel 6 die Methode der Fast Expression Templates (FET) entwickelt, zunachst unter Verwendung
von template-nummerierten Variablen, sowie darauf aufbauend eine rein typbasierte Variante. Die
Effizienz dieser FET-Implementierungen wird dann auf gangigen Rechnern sowie Hochleistungsrech-
nern mit der Performance von traditionellen ET-Varianten und reinen C-Codes ohne ET verglichen.
Abschließend werden noch einige praktische Programmierhinweise zur Anwendung der FET-Technik
aufgefuhrt. Kapitel 7 enthalt mehrere Methoden zum besseren Einsatz der ET- bzw. FET-Techniken.
Dies erstreckt sich uber die Typ-Minimierung mittels Traits, die Anwendung der ET auf verschiedenar-
tige Datenstrukturen und die Speicherung von ET-Ausdrucken als Objekte bzw. mittels Typkapselung.
Weiterhin behandelt werden Methoden zur sicheren Matrix-Vektor-Multiplikation. In Kapitel 8 wer-
den die Ubersetzungszeiten von ET-Programmen untersucht, sowie Implementierungsvarianten und
Techniken zur Verkurzung der Compilierungsdauer. Dabei steht vor allem die Untersuchung des GNU
C++-Compilers im Mittelpunkt. Abschließend fasst Kapitel 9 die erarbeiteten Weiterentwicklungen
der ET-Technik zusammen und diskutiert die Grenzen dieser Methode sowie die weitergehende An-
wendungen im wissenschaftlichen Rechnen.
Der dritte Teil dieser Arbeit umfasst die Anwendung der ET-Implementierungstechnik zur Losung
von partiellen Differentialgleichungen, insbesondere mittels der Finiten Elemente Methode (FEM). In
Kapitel 10 werden die Grundlagen der FEM unter Auflistung der einzelnen Schritte zur Durchfuhrung
einer FE-Diskretisierung erlautert. Kapitel 11 umfasst die Programmierung, die Funktionsweise und
den Programmaufbau der Template-Bibliothek Colsamm durch Vergleich mit den vorher zusammenge-
tragenen mathematischen Grundlagen der FEM. Anschließend werden einige Anwendungen von Col-
samm in praxisrelevanten Problemstellungen wie etwa im optischen Fluss oder der Berechnung von Di-
polmodellen von Gehirnstromen prasentiert. Kapitel 12 umfasst weitere Anwendungen der ET-Technik,
unter anderem Methoden zur Schleifenmanipulation, die Implementierung der logischen Verknupfung
von Gebieten. Als Abschluss fasst Kapitel 13 die prasentierten Ergebnisse zusammen und umreißt
weitere Einsatzgebiete und Anwendungsmoglichkeiten der ET-Technik.
4
Teil I
Expression Templates – Grundlagen,
Entstehung und Analyse
KAPITEL 2. SPRACHGRUNDLAGEN UND PROGRAMMIERKONZEPTE
2 Sprachgrundlagen und
Programmierkonzepte
In den folgenden Abschnitten werden die programmiersprachlichen Grundlagen sowie die spater ange-
wendeten Techniken zur Realisierung domanenspezifischer Programmierung prasentiert und die dazu
notigen Implementierungstechniken in C++ zusammengefasst.
2.1 Domain-specific Embedded Languages
Eine domanenspezifische Programmiersprache (domain-specific language, DSL) beschreibt eine Pro-
grammiersprache, die sich zum Implementieren bestimmter Problemstellungen auf Grund der Schnitt-
stellen, der Funktionen und Performance, besonders gut eignet [VS06]. Dabei sind die softwaretech-
nischen Anforderungen der Programmentwicklung von der fachlichen Erstellung von Algorithmen ge-
trennt. Integriert man eine DSL in einer bestehenden Programmiersprache, so spricht man von einer
domain-specific embedded language (DSEL). Solche DSELs, die in eine bereits existierende Sprache
eingebettet sind, haben den Vorteil, dass sie durch gangige Compiler ubersetzt werden konnen. Diese
ubernehmen dann die komplette Syntax- und Semantikanalyse. Außerdem ist eine Programmierung
mit bereits bekannter Syntax moglich. Daruber hinaus sind DSEL-Implementierungen durch die Ver-
breitung der bestehenden Compiler sehr leicht auf verschiedene Plattformen portierbar.
Zur Realisierung solcher DSEL fur mathematische Problemstellungen bietet C++ mit dem Uberladen
von Operatoren eine Moglichkeit, um sehr intuitive mathematische Schnittstellen zu definieren. Der
entscheidende Vorteil der Verwendung von Operatoren anstatt herkommlicher Funktionen liegt in der
in-order Schreibweise (Operand - Operator - Operand), was der naturlichen mathematischen Notation
entspricht. Bei entsprechender Definition konnen die Operatoren auch mit vollig neuen Bedeutungen
ausgestattet werden, wobei hierbei darauf zu achten ist, dass die Verstandlichkeit der Implementierung
bestehen bleibt.
2.2 Benutzerfreundliche, mathematische Schnittstellen
In C++ konnen zu einer Funktion mehrere Varianten mit gleichen Funktionsnamen definiert werden,
die sich jedoch in ihrer Parameterliste unterscheiden mussen [Str00]. Dieses sog. Uberladen von Funk-
tionen ermoglicht es beim Programmieren einheitlich benannte Schnittstellen zu schaffen, die jedoch
entsprechend der eingesetzten Parameter ganz unterschiedliche Implementierungen und Wirkungen
haben konnen. Damit wird die Verwendung einer entsprechend angelegten Bibliothek einfacher, da
der Anwender nicht erst die zum jeweiligen Problem passende Funktion suchen muss. Zusammen mit
dem spater aufgefuhrten Template-Mechanismus sowie der Template-Spezialisierung kann dies als sehr
geschicktes Mittel eingesetzt werden, um komplizierte Datentypen verarbeiten zu konnen [AG05], ohne
bei der Benutzung die exakten Typ angeben zu mussen.
Funktionen konnen nur mittels unterschiedlicher Parameterlisten uberladen werden, wobei auch die
internen Typkonvertierungen beim Implementieren zu berucksichtigen sind, um eventuelle Mehrdeutig-
keiten beim Ubersetzen zu vermeiden. Ein Uberladen ausschließlich bezuglich des Ruckgabetyps einer
Funktion ist nicht moglich, da ein Funktionsaufruf nach dem C++-Standard kontextunabhangig sein
7
2.2. BENUTZERFREUNDLICHE, MATHEMATISCHE SCHNITTSTELLEN
soll, d.h. der Aufruf einer Funktion mit den identischen Parametern in jedem Kontext zu dem gleichen
Ergebnistyp fuhren muss.
Finden sich beim Ubersetzen eines Programms zu einem Funktionsnamen mehrere Varianten mit
unterschiedlichen Parameterlisten, so bestimmt der Compiler diejenige uberladene Version der Funkti-
on, deren Parametertypen am besten zum aktuellen Funktionsaufruf passen. Dazu definiert der C++
Standard zur Bestimmung der passenden Funktion den folgenden festgelegten Ablauf von untersuchten
Kriterien [Str00]:
1. Genaue Ubereinstimmung der Parametertypen bzw. uber die Anwendung trivialer Konvertie-
rungen auflosbar (Array-Name nach Zeiger, Funktionsname nach Funktionszeiger und T nach
const T).
2. Identifikation der Typen mittels sog. Promotionen (Umwandeln in einen sinnvollen, großeren Typ
(bool nach int , char nach int , short nach int sowie entsprechende Umwandlungen mit unsigned; float
nach double und double nach long double)
3. Eine Ubereinstimmung der Typen der Parameterliste nach Anwendung bestimmter Standard-
konvertierungen (z.B. int nach double, Abgeleitete Klasse * nach Basis Klasse*).
4. Ubereinstimmung der Parametertypen nach der Anwendung von benutzerdefinierten Konvertie-
rungsoperatoren (siehe Abschnitt 2.2.2).
5. Ubereinstimmung mittels der Erweiterung fur eine variable Anzahl von Parametern in einer
Funktionsdeklaration (z.B. void foo( int a, ...) ).
Nur wenn der Compiler mittels Durchlauf dieser Liste genau eine Ubereinstimmung findet, kann er
die passende Funktion bestimmen und entsprechend ubersetzen. Beim Behandeln von uberladenen
Funktionen konnen sich nach dieser Liste aber auch Mehrdeutigkeiten ergeben, die zu einem Fehler
beim Ubersetzen fuhren und vom Programmierer bereinigt werden mussen.
Neben dem Uberladen von Operatoren gibt es noch eine weitere Variante zur Programmierung
flexibler Funktionsschnittstellen, die Definition von Default-Parametern. Beginnend vom Ende der
Parameterliste einer Funktion konnen in Funktionsdeklaration Werte angegeben werden, die eingesetzt
werden, falls das entsprechende Argument beim Aufruf nicht ubergeben wird. Werden solche Default-
Parameter verwendet, mussen diese luckenlos vom letzten Parameter an definiert werden, bis zu dem
ersten Parameter, fur den kein Defaultwert vorgegeben werden soll.
2.2.1 Uberladen von Operatoren
Die Programmierung in C++ erlaubt neben dem Uberladen von Funktionen auch einen Großteil der
gangigen, zumeist mathematischen Operatoren fur benutzerdefinierte Datentypen neu zu definieren
[Str00]. Grundsatzlich werden diese Operatoren genauso wie Funktionen uberladen, indem Versio-
nen der Operatoren mit unterschiedlichen Parametertypen deklariert werden. Bis auf den Klammer-
Operator (operator()) haben jedoch alle Operatoren eine feste Anzahl von Parametern, die nicht ver-
andert werden kann. Bei der Suche nach derjenigen uberladenen Version, deren Parameterliste am
besten mit den Argumenten des aktuellen Aufrufs des Operators ubereinstimmt, werden die selben
Konvertierungsregeln angewandt, wie sie zuvor fur herkommliche Funktionen aufgefuhrt sind. Trotz
der Ahnlichkeit haben Operatoren noch einige Besonderheiten, die in der folgenden Auflistung kurz
zusammengefasst werden.
• Die Mehrheit der Operatoren kann entweder als Nicht-Elementfunktion oder Memberfunktion
definiert werden. Bei der Deklaration der Operatoren als Memberfunktionen fungiert das this -
Objekt als linker Operand. Alle Zuweisungsoperatoren, sowie die Operatoren operator[ ], operator()
und operator−>, mussen jedoch als nicht-statische Elementfunktionen definiert werden um sicher-
zustellen, dass es sich beim ersten Operanden um ein veranderbares Objekt ( lvalue ) handelt.
8
KAPITEL 2. SPRACHGRUNDLAGEN UND PROGRAMMIERKONZEPTE
• Durch das Uberladen kann die Wirkung von Operatoren neu definiert werden. Trotz der neuen
Funktionsweise bleibt die durch C++ definierte Prioritatenregelung der Operatoren erhalten. Es
gilt also weiterhin Punkt vor Strich. Auch die weiteren Operatoren, die noch uberladen wer-
den konnen, haben eine festgesetzte Prioritat, die nicht verandert werden kann. Eine komplette
Prioritatenliste findet man ebenfalls in [Str00].
• Operatoren haben die sehr benutzerfreundliche Eigenschaft, dass sie – anders als Funktionen oder
Klassenmethoden – in in-order Notation verwendet werden. Diese Schreibweise entspricht genau
der mathematischen Formulierung, was naturlich die Implementierung numerischer Probleme
wesentlich erleichtern kann. Dabei ist jedoch bei binaren Operatoren noch zu beachten, dass im
Standard nicht festgelegt ist, ob zuerst der erste oder der zweite Operand ausgewertet wird.
2.2.2 Benutzerdefinierte Konvertierungsoperatoren
Zusatzlich zu den Operatoren in C++ wird die Definition von benutzerdefinierten Konvertierungsope-
ratoren zum Spezifizieren von Typumwandlungen betrachtet. Die Umwandlungen eines Datentyps in
einen anderen sind in C++ wegen der strengen Typprufung sehr eingeschrankt. So sind nach dem
Standard nur bestimmte Konvertierungen der fundamentalen Datentypen moglich, sowie eine Kon-
vertierung eines abgeleiteten Typs in den Basisklassentyp. Sollen nun zusatzliche Casts eingefuhrt
werden, so kann dies teilweise durch die Definition entsprechender Konstruktoren erreicht werden.
Folgende Falle konnen aber nicht mittels Konstruktoren realisiert werden [Str00]:
• die implizite Konvertierung eines benutzerdefinierten Typs in einen fundamentalen Typ, und
• eine Konvertierung in eine bereits definierte Klasse, ohne die bestehende Klasse zu verandern.
Fur diese Falle ist die Einfuhrung von klassenspezifischen Konvertierungsoperatoren moglich, welche
die Umwandlung eines Objekts einer neu definierten Klasse in einen bereits bestehenden Datentyp
definieren. Mittels dieser Cast-Operatoren kann gezielt die Umwandlung zwischen Typen eingefuhrt
werden, die der Compiler nicht von selbst auflosen kann.
Ist TYPE ein fundamentaler Datentyp oder eine bereits definierte Klasse, so kann man einen solchen
Konvertierungsoperator folgendermaßen definieren:
c l a s s X . . .
ope ra to r TYPE ( ) ;
;
X : : ope ra to r TYPE ( ) . . .
Dabei ist besonders zu bemerken, dass der Cast-Operator keinen Ruckgabetyp besitzt. Zusatzlich
kann man auch die Umwandlung von konstanten Objekten oder auch Zeigern oder Referenzen auf
Objekte spezifizieren, z.B.:
c l a s s X
. . .
ope ra to r const TYPE& () const ;
;
Dieser Cast-Operator wird dann in all jenen Fallen aufgerufen, in denen der Compiler diese Um-
wandlung benotigt um das aktuelle Objekt in den passenden Typ umzuwandeln. Das geschieht unter
anderem auch bei expliziten Casts, z.B. static cast . Allgemein wird dazu geraten, solche benutzerdefi-
nierten Cast-Operatoren nur sehr gezielt einzusetzen, andernfalls kann dies leicht zu undurchsichtigen
Mehrdeutigkeiten fur den Compiler fuhren.
9
2.3. TEMPLATES IN C++
2.3 Templates in C++
Das Einsetzen von Datentypen als Klassenparameter in C++ wird Template (Schablone) genannt. Die-
se Moglichkeit wurde erst spater in den Sprachstandard aufgenommen, hauptsachlich um die Definition
von typunabhangigen Datenstrukturen zu verbessern.
Die strenge Typkontrolle in C++ verlangt vom Programmierer, dass Variablen, Parameter und
Ruckgabewerte von Funktionen mittels spezifischen Typen deklariert werden. Es gibt jedoch viele
Algorithmen, die trotz verschiedener Grunddatentypen gleich oder sehr ahnlich implementiert werden.
Als Beispiel stelle man sich hierzu die Realisierung von Sortieralgorithmen oder Container-Klassen vor.
Will man in C oder C++ (ohne Templates) solche Programme nicht fur jeden benotigten Typ aus-
schreiben, so gibt es zwei Varianten, um mit allgemeinen Grunddatentypen arbeiten zu konnen. Man
konnte entweder mit typunsicheren void*-Zeigern arbeiten, oder sich entsprechende Makros definieren,
welche den aktuellen Typ fur den aktuellen Programmlauf ersetzen. Doch diese herkommlichen Losun-
gen haben genauer betrachtet entscheidende Nachteile. Bei der Programmierung mit void*-Zeigern kann
der Compiler fur diesen Teil nur noch eine sehr eingeschrankte Typprufung durchfuhren. Daruber hin-
aus sind fur diese void*-Variablen keine Datentypgroßen und keine Klassenmethoden bekannt, und die
Programmcodes werden schwer lesbar. Auch die Verwendung von Makros halt die Implementierung
kurz, doch ist die Arbeit mit diesem einfachen Ersetzungsmechanismus fehleranfallig, da eine Syntax-
prufung erst nach dem Precompiling durchgefuhrt wird. Dies fuhrt meist zu schwer nachvollziehbaren
Fehlermeldungen, da sie sich nicht auf den ursprunglichen Programmcode beziehen [VJ03].
Zur Verbesserung dieser Implementierungstechniken wurde mit dem Template-Mechanismus ein ge-
nerisches Programmierparadigma in den C++-Sprachstandard eingefuhrt, welches die Flexibilitat der
void*-Zeiger bzgl. der Typwahl sowie eine vollstandige Typprufung erfullt. Templates ermoglichen die
Definition von Funktionen und Klassen, die von einem oder mehreren Typparametern abhangen. Um
namentliche Verwechslungen mit den Parametern der Argumentliste einer Funktion auszuschließen,
werden die Typparameter im Weiteren als Template-Parameter bezeichnet. Daneben heißen Funktio-
nen oder Klassen mit Template-Parametern templatisierte Funktionen bzw. Klassen.
Die Programmierung mit Templates erlaubt eine Implementierung ohne den zugrunde liegenden
Datentyp spezifizieren zu mussen. Die Festlegung des verwendeten Template-Typs findet erst bei der
Verwendung des templatisierten Codes statt, wobei die Semantikanalyse nur von den Eigenschaften des
tatsachlich verwendeten Typs abhangig ist. Da die Typspezifizierungen bereits zur Ubersetzungszeit
feststehen, kann der Compiler eine vollstandige Semantik- sowie Typprufung durchfuhren. Bei der
Definition und Verwendung von templatisierten Klassen und Funktionen treten einige Besonderheiten
auf, die im Folgenden kurz zusammengefasst werden.
2.3.1 Definition von templatisierten Klassen und Funktionen
Nach dem C++-Standard konnen Integer- und Enumerationswerte, sowie fundamentale und benut-
zerdefinierte Datentypen als Template-Typen verwendet werden. Die Deklaration von templatisier-
ten Klassen und Funktionen geschieht durch das Schlusselwort template, gefolgt von der Template-
Parameterliste in spitzen Klammern. Dabei kann fur einen Datentyp entweder eines der Schlusselworte
class oder typename verwendet werden. Im Rahmen dieser Arbeit wird class immer fur benutzerdefinierte
Datentypen benutzt, typename nur fur fundamentale Typen eingesetzt.
// Template I n t e g e r
template < i n t I n t eg e rVa l u e > c l a s s X 1 ;
// Template bekommt e i n en fundamenta len Datentyp
template <typename A> c l a s s X 2 ;
// Template−L i s t e mehre r e r b e n u t z e r d e f i n i e r t e r Datentypen
template <c l a s s A, c l a s s B> c l a s s X 3 ;
10
KAPITEL 2. SPRACHGRUNDLAGEN UND PROGRAMMIERKONZEPTE
Die Deklarationen von Funktionen mit Template-Parametern werden analog gehandhabt. Die Tem-
plate-Technik in C++ bietet einige spezifische Anwendungsmoglichkeiten, die im Folgenden umrissen
werden. Die nachstehende Aufstellung dient als Darstellung der wichtigen Aspekte von Templates,
bietet aber keine vollstandige Beschreibung der Technik. Dazu wird auf [VJ03] verweisen.
• Ahnlich wie bei der Definition der Argumentlisten von Funktionen, konnen auch fur Template-
Parameter Defaultwerte angegeben werden. Die Definition von Default-Templates ist nur am
Ende der Template-Parameterliste moglich. Beim Auflosen der Liste der Templates gelten die
gleichen Regeln wie bei den Default-Funktionswerten.
• Verwendet man templatisierte Klassen in einem Programm, mussen alle Template-Parameter
angegeben werden, die nicht durch Defaultwerte festgelegt sind. Die Templates werden in spitzen
Klammern nach dem Klassennamen aufgefuhrt. Sollte eine komplette Liste mit Default-Templates
definiert sein, mussen nach dem Klassennamen dennoch die leeren spitzen Klammern angegeben
werden. Ist ein Template-Argument selbst ein templatisierter Datentyp, muss darauf geachtet
werden, dass zwischen zwei spitzen Klammern immer ein Leerzeichen stehen muss.
• Funktionen haben im Vergleich zu Klassen den Vorteil, dass die Templates aus der Parameterlis-
te bestimmt werden konnen (type deduction). Dabei muss keine explizite Angabe der Templates
beim Aufruf der Funktion geschrieben werden, wenn der C++-Compiler die Typen der Template-
Parameter komplett aus der Argumentliste ableiten kann. Daruber hinaus ist es trotzdem moglich,
templatisierte Funktionen zu verwenden, deren Template-Parameter nicht vollstandig durch die
Parameterliste festgelegt sind. Diese mussen dann zusatzlich beim Aufruf angegeben werden. Im
Falle von objektunabhangigen Funktionen werden Template-Parameter – analog zu den Klassen
– in spitzen Klammern nach dem Funktionsnamen angegeben. Bei templatisierten Klassenmetho-
den muss dem Compiler mit dem Schlusselwort template unmittelbar vor dem Funktionsnamen
signalisiert werden, dass es sich um einen Funktionsaufruf handelt [Str00]. Sind die Templates teil-
weise durch die Funktionsargumente festgelegt, so gelten die gleichen Auflosungsbestimmungen
wie bei den Default-Template-Parametern.
• Fur templatisierte Klassen und Funktionen konnen Spezialisierungen, fur Template-Klassen auch
partielle Spezialisierungen definiert werden, um dadurch fur bestimmte Template-Argumente ge-
sonderte Implementierungen zu verwenden. Dazu definiert man die (partielle) Spezialisierung
mit Angabe der festen Template-Parameter. Der Compiler wahlt bei entsprechenden Template-
Parametern die passende (Teil-)Spezialiserung aus und vervollstandig gegebenenfalls die Instan-
tiierung. Dabei ist jedoch darauf zu achten, dass bei mehreren Template-Parametern keine Mehr-
deutigkeiten fur den Compiler entstehen [Str00].
2.3.2 Template-Instantiierung
Ein Programmcode, der von Templates abhangt, wird nach dem C++-Standard nur dann erzeugt,
wenn er in der Implementierung benotigt wird. Bei templatisierten Klassen und Funktionen sorgt der
Compiler implizit dafur, dass die notigen Instantiierungen bzgl. des eingesetzten Datentyps bei ihrem
ersten Aufruf zur Verfugung stehen. Dabei werden aber im Falle einer Template-Klasse nur die Member-
Templates erzeugt, die auch wirklich im weiteren Programm aufgerufen werden. Dies bedeutet, dass
man templatisierte Implementierungen mit Datentypen initialisieren kann, die bestimmte Methoden
des Template-Codes gar nicht zur Verfugung stellen. Diese zum Teil unvollstandige Instantiierung
fuhrt naturlich bei der Softwareentwicklung zu dem Nachteil, dass der Programmierer nur schwer die
Robustheit seiner Implementierung uberprufen kann. Konkrete Semantik-Fehler kann der Compiler
nur bei entsprechenden Instantiierungen entdecken.
Zusatzlich zur impliziten Code-Erzeugung des Compilers kann bei einer templatisierten Klasse oder
Funktion auch eine explizite Instantiierung erfolgen. Damit kann der Programmierer sicherstellen, dass
11
2.3. TEMPLATES IN C++
der Code fur bestimmte Datentypen auch sicherlich erzeugt wird. So kann man bei der Entwicklung
von Software-Bibliotheken gewissen Mangeln der Instantiierung vorbeugen.
Die templatisierten Implementierungen werden beim Instantiieren in zwei Stufen uberpruft. Zunachst
wird der generische Programmcode (ohne eingesetzte Datentypen) auf Fehler in der Syntax untersucht.
Nach der Instantiierung wird zusatzlich der erzeugte Code auf kontextabhangige Semantik, wie zum
Beispiel verwendete Methoden oder Operationen uberpruft.
Wegen der zugrunde liegenden Trennung von Implementierung und Instantiierung muss der Com-
piler wahrend des Ubersetzens den kompletten templatisierten Programmcode kennen. Es genugt nun
nicht mehr, wie sonst ublich, die Deklarationen in einer Header-Datei zu kapseln, die dann alleine in die
notigen Programme eingebunden wird. Vielmehr mussen alle Implementierungen, die von Templates
abhangen, wahrend des Instantiierungsschrittes verfugbar sein. Dabei sorgt jedoch der Compiler bei
Programmen mit mehreren Ubersetzungseinheiten dafur, dass keine Mehrfach-Instantiierungen von
templatisierten Klassen mit den gleichen Template-Parametern auftreten.
2.3.3 Meta-Template-Programmierung
Die Template-Programmierung in C++ eroffnet mit den generischen Paradigmen ein weiteres wichtiges
Feld der Code-Entwicklung, die Erzeugung von Programmen zur Ubersetzungszeit. Diese sog. Meta-
Programmierung bezeichnet die Implementierung von Programmen, die dazu dienen neue Programme
zu generieren [AG05]. Die Besonderheit der Verwendung von Templates in C++ besteht darin, dass
diese Programmerzeugung wahrend der Ubersetzungszeit stattfindet.
Da durch Template-Spezialisierungen, je nach den eingesetzten Typen, verschiedene Programm-
teile instantiiert werden, kann somit sukzessive eine Codegenerierung erreicht werden. Dadurch wird
wahrend der Ubersetzungszeit ein Programmcode erzeugt, der anschließend vom C++-Compiler weiter
ubersetzt werden kann.
2.3.4 Traits
Eine besondere Anwendung finden die (partiellen) Spezialisierungen von templatisierten Klassen bei
der Implementierung von sog. Traits. Diese erlauben die Abbildung von Typinformationen auf Werte,
Funktionen und Typen, beschrieben unter anderem in [AG05].
Betrachten wir dazu als einfaches Beispiel einen Programmcode, mittels dem bestimmt werden kann,
ob es sich bei einem eingesetzten Typen um einen Integer handelt. Dazu benutzen wir die Klasse
Is Integer , die einen booleschen Wert hat, der im allgemeinen Fall auf false und nur fur die Spezialisie-
rung fur int auf true gesetzt wird.
template <typename Type>
s t r u c t I s I n t e g e r
s t a t i c boo l i s i n t e g e r = f a l s e ;
;
template < >
s t r u c t I s I n t e g e r <i n t >
s t a t i c boo l i s i n t e g e r = t rue ;
;
Damit kann nun bestimmt werden, ob es sich bei einem eingesetzten Datentyp um einen Integer
handelt oder nicht. Sinnvoll ist dies besonders in templatisierten Klassen oder Funktionen, in denen
nur fur bestimmte Datentypen – hier Integer – eine zusatzliche Funktionalitat eingefuhrt werden soll,
ohne eine explizite Spezialisierung. Da diese Auswertung bereits zur Ubersetzungszeit stattfindet, sind
damit auch Abbildungen von Template-Parametern auf Typen moglich, die als Template-Argumente in
eine templatisierte Klasse eingesetzt werden. Traits finden besonders in der Meta-Template-Bibliothek
BOOST [DA03] eine breite Anwendung. Hier werden Sammlungen von templatisierten Klassen und
12
KAPITEL 2. SPRACHGRUNDLAGEN UND PROGRAMMIERKONZEPTE
entsprechende Spezialisierungen bereit gestellt, die eine Vielzahl der wichtigen Auswertungen von Ty-
pen implementieren.
template <typename Type>
c l a s s DataSt ruc tu r e
boo l i n t e g e r ;
. . .
. . .
i n t e g e r = I s I n t e g e r <Type > : : i s i n t e g e r ;
;
2.3.5 Templategesteuerte Vererbung
Vererbung und Polymorphie stellen fur die Implementierung benutzerfreundlicher Software ein weiteres
wichtiges Feature dar. Dadurch ist es moglich, einheitliche Schnittstellen zu definieren, sowie objek-
torientierte Abstraktionen einzufuhren. Leider haben Implementierungen mittels abstrakter Klassen
den Nachteil, dass bei virtuellen Funktionen nur eine spate Bindung zur Laufzeit moglich ist. Gerade
bei der Verwendung von Funktionen mit einfachem Funktionsrumpf und haufigem Aufruf fuhrt dies zu
einem großen Overhead, der die Leistung eines Programms entscheidend beeintrachtigt. Optimierungen
wie Inlining sind bei einer spaten Bindung nicht moglich.
Eine Vererbung in Abhangigkeit von Template-Parametern bildet eine wichtige Grundlage fur leis-
tungsfahige Programme. Durch die bekannten Typinformationen kann eine dynamische Bindung ver-
mieden werden. Dabei kann die Basisklasse von den Template-Argumenten der Ableitung abhangen,
bzw. einer dieser Template-Parameter selbst Basisklasse werden. Mittels diesen template-abhangigen
Vererbungen lassen sich je nach eingesetzten Template-Parametern die Spezialisierungen von templati-
sierten Klassen zu davon abgeleiteten Klassen dazufugen. Besonders interessant ist dies bei Implemen-
tierungen fur unterschiedlich dimensionale Problemstellungen, bei denen anbangig von der Dimension
verschiedene Berechnungsformeln verwendet werden mussen.
template <c l a s s X>
c l a s s A . . . ;
template <c l a s s X>
c l a s s B : pub l i c X, pub l i c A<X> . . . ;
2.3.6 CRTP (Curiously Recurring Template Pattern)
Dienen die abstrakten Klassen einer Bibliothek lediglich als Schnittstellen fur die Parameterubergabe in
Funktionen oder Memberfunktionen, so kann jedoch eine besser optimierbare Vererbung implementiert
werden. Diese sog. CRTP-Technik wird oft in Kombination mit dem Barton-Nackman-Trick prasentiert
und oft auch mit diesem verwechselt [VJ03].
template <c l a s s A>
s t r u c t Bas e C l a s s
const A& ca s t ( ) const r e t u r n s t a t i c c a s t <const A&>(∗ t h i s ) ;
;
. . .
c l a s s De r i v a t i v e : pub l i c Base C la s s <De r i v a t i v e > . . . ;
Die CRTP-Technik implementiert einen Vererbungsmechanismus der bereits zur Ubersetzungszeit
aufgelost werden kann. Dazu muss eine templatisierte Basisklasse definiert werden, die bei der Ver-
erbung den Typ der abgeleiteten Klasse als Template-Typ erhalt. Dadurch kann jedes Objekt der
Basisklasse in den Typ des aktuellen abgeleiteten Objekts umgewandelt werden. Somit kann der Com-
piler die notigen Typen der Objekte bestimmen und alle ublichen Optimierungen durchfuhren.
13
2.4. OPTIMIERUNGEN VON C++-COMPILERN
Mittels der CRTP-Implementierung kann also jedes Objekt der Basisklasse – der Typ der abgelei-
teten Klasse ist bekannt – in ein Objekt des Typs der abgeleiteten Klasse umgewandelt werden. Die
Verwendung vor einem Funktionsaufruf sieht damit folgendermaßen aus:
template <c l a s s A>
vo id f oo ( const Base C la s s <A>& ob j e c t )
ob j e c t . c a s t ( ) . bar ( ) ;
;
Zusatzlich konnen fur Situationen, in denen das abgeleitete Objekt als Argument in Funktionen oder
Konstruktoren ubergeben werden soll, automatische Konvertierungen einfuhrt werden. Dazu wird in
der Basisklasse ein Cast-Operator definiert, der im Wesentlichen die Funktion des vorher gezeigten cast
ubernimmt.
template <c l a s s A>
s t r u c t Bas e C l a s s
ope ra to r const A& () const
r e t u r n ∗ s t a t i c c a s t <const A∗>( t h i s ) ;
;
Hierbei ist besonders darauf zu achten, dass der static cast uber eine Konvertierung der Zeiger aus-
gefuhrt wird, nicht durch eine Umwandlung der Referenzen. Sonst kann es – bei machen Compilern,
z.B. Intel Compiler oder alteren GNU Versionen – passieren, dass dieser Operator sich selbst aufruft,
was vom Compiler nicht aufgedeckt werden kann. Dadurch, dass der static cast intern mit den Zeigern
auf das Objekt aufgerufen wird, umgeht man dieses Problem. Insgesamt ist es damit fur den Compi-
ler moglich, ein Objekt vom Typ der Basisklasse in den abgeleiteten Typ umzuwandeln, wenn dieser
innerhalb von Funktionsparametern benotigt wird.
2.4 Optimierungen von C++-Compilern
Da die Implementierungstechniken dieser Arbeit darauf ausgelegt sind, effiziente Programmcodes zu
ergeben, werden neben der richtigen Art und Weise der Programmierung auch leistungsstarke Optimie-
rungsmechanismen der Compiler benotigt. Nachfolgend werden einige ausgewahlte, fur das Verstandnis
dieser Arbeit wichtige, Optimierungskonzepte vorgestellt.
2.4.1 Elimination temporarer Objekte
Die Bezeichnung temporare Objekte umfasst im Rahmen dieses Teilabschnitts Werte, die wahrend des
Programmlaufs auf dem Stack abgelegt werden. Das Erzeugen und Zerstoren der temporaren Objek-
te kann je nach Typ durchaus hohe Kosten erzeugen und so die Performance eines Programms sehr
beeintrachtigen. In der (deutschsprachigen) Literatur werden auch lokale Objekte als temporar bezeich-
net, doch sind die lokalen Objekte von den nachstehenden Optimierungen nicht betroffen. Prinzipiell
kann man die Situationen, die den Compiler dazu zwingen temporare Objekte zu erzeugen, auf drei
wesentliche Ursachen zuruckfuhren.
Parameterubergabe bei Funktionen
Die Ubergabe by value von Objekten in Funktionsparametern hat die Erzeugung eines temporaren
Objektes zur Folge, wenn keine zusatzlichen Optimierungen angewandt werden. Betrachten wir zum
Beispiel eine Funktion, die als Ubergabeparameter ein Objekt vom Typ complex<double> besitzt, und
folgendermaßen deklariert wird.
14
KAPITEL 2. SPRACHGRUNDLAGEN UND PROGRAMMIERKONZEPTE
vo id f oo ( s t d : : complex<double> v a l u e ) ; // by v a l u e
In diesem Fall wird bei jedem Aufruf der Funktion foo das ubergebene complex<double>-Objekt kopiert.
Je nach Große des Objekts und je nach Haufigkeit der Aufrufe der Funktion kann dies die Effizienz der
Programmierung beeintrachtigen. Viele C++-Compiler konnen in diesen Fallen zwar durch entspre-
chende Optimierungen die Erzeugung von temporaren Objekten vermeiden, doch durch eine Ubergabe
by reference kann die Programmierung so abgeandert werden, dass keinerlei temporare Objekte ent-
stehen.
vo id f oo ( const s t d : : complex<double>∗ v a l u e ) ; // by r e f e r e n c e
vo id f oo ( const s t d : : complex<double>& va l u e ) ; // by r e f e r e n c e
Die Deklarierung als const garantiert dabei, dass auf das ubergebene Objekt in der Funktion nicht
schreibend zugegriffen werden darf und dieses so unverandert bleibt.
Temporare Objekte bei impliziter Typkonvertierung
Ruft man die obige Funktion statt mit einem komplexen Wert mit einem Wert vom Typ double auf,
so verwendet der Compiler implizit noch den entsprechenden Konstruktor der Klasse complex, der aus
einem double ein Objekt vom Typ complex<double> erzeugt.
f oo ( 1 . ) ; // r u f t i n t e r n den Kons t rukto r complex ( doub l e ) au f
Durch diese implizite Konvertierung der Variablen, die lediglich fur die Parameterubergabe benotigt
wird, entsteht wiederum ein temporares Objekt. Ahnlich zum vorherigen Fall konnen aber viele Com-
piler auch hier Optimierungen ansetzen und diese Erzeugung temporarer Objekte vermeiden.
Wie bereits erwahnt, kann der Compiler nur gewisse Konvertierungen implizit durchfuhren. Darunter
fallt – wie hier – der Aufruf entsprechender Konstruktoren, der aber dadurch vermieden werden kann,
indem die Konstruktoren als explicit deklariert werden. Somit durfen diese Konstruktoren nicht fur
die Typkonvertierung verwendet werden. Benutzerdefinierte Konvertierungsoperatoren dagegen werden
speziell dafur eingefuhrt, dem Compiler zusatzliche Umwandlungen zu ermoglichen. Außerdem konnen
diese zusatzlichen Konvertierungsoperatoren in bestimmten Fallen so definiert werden, dass sie lediglich
(konstante) Referenzen zuruckgeben und keine vollen Objekte. Damit wird ein zusatzliches Erzeugen
von temporaren Objekten verhindert.
Ruckgabewert-Optimierung
Wird innerhalb eines Funktionsrumpfes ein Objekt erzeugt, das den Ruckgabewert einer Funktion bil-
den soll, so kann die Ruckgabe nicht by reference erfolgen, da das lokal erzeugte Objekt nach Verlassen
der Funktion wieder geloscht wird. Also muss in diesen Fallen eine Ruckgabe by value erfolgen, die
aber wieder durch eine Ruckgabewert-Optimierung des Compilers beim Ubersetzen behoben werden
kann.
s t d : : complex<double> foo2 ( ) r e t u r n s t d : : complex<double > ( 1 . ) ;
2.4.2 Inlining
Die Optimierungen mittels Inlining von Funktionen sind eine Methode zur Reduzierung der Kosten von
Funktionsaufrufen. Durch Voranstellen des Schlusselwortes inline wird dem Compiler angegeben, dass
die Funktion bei jedem ihrer Aufrufe durch ihren Funktionsrumpf ersetzt werden soll. Die Definition
einer Inline-Funktion muss dem Compiler in jedem Ubersetzungsabschnitt zur Verfugung stehen, da die
Funktionsdefinition fur die Optimierung komplett bekannt sein muss. Bei Methoden, die innerhalb der
Klasse definiert werden, geschieht das Inlining im Allgemeinen automatisch. Dagegen ist ein Inlining
15
2.4. OPTIMIERUNGEN VON C++-COMPILERN
bei virtuellen Funktionen nicht moglich, da beim Compilieren noch nicht bekannt ist, welche Funktion
tatsachlich aufgerufen werden soll.
Ist eine (Member-)Funktion als inline deklariert, so fugt der Compiler also den Rumpf der Funktion
– im Prinzip – an der Stelle ihrer Benutzung ein. Der so optimierte Programmcode kommt fur diese
Funktionen ohne den Aufruf und den Sprung an die entsprechende Speicherstelle, an der die Funktion
liegt, aus. Daruber hinaus kann der Compiler nun uber die Grenzen der Funktion hinweg optimieren,
z.B. Auswertung von Konstanten, Bestimmung von Abhangigkeiten, etc.
Das Inlining sollte prinzipiell nur fur Funktionen mit kleinem Funktionsrumpf eingesetzt werden.
Da der Code von Inline-Funktionen bei jedem ihrer Aufrufe benotigt wird, kommt die Programmie-
rung mehrfach im resultierenden Programm vor. Dies fuhrt zu langen Ubersetzungszeiten sowie großen
Executables. Also ist bei der Implementierung genau abzuwagen, welche Funktionen als inline dekla-
riert werden sollten. Zum Optimieren langer, geschachtelter Aufrufe von Inline-Funktionen braucht ein
Compiler oft unerwartet viel Speicher, was auch zu ubermaßig langen Ubersetzungszeiten fuhrt. Bei
besonders intensiven Inline-Optimierungen kann der Compiler auch an vordefinierte Grenzen stoßen,
so dass er dann das Inlining unterbricht. Mit diesem optimierten Code wird dann ein Funktionsaufruf
erzeugt und mit dem weiteren Inlining fortgefahren. Zur Uberprufung, ob die vorgegebenen Grenzen
uberschritten werden, bieten die meisten C++-Compiler das Winline-Flag an, das entsprechende War-
nungen ausgibt. Insbesondere die neueren Versionen des GNU-Compilers benennen sogar genau die
Parameter, die verandert werden mussen.
2.4.3 Aliasing
Bestehen in einem Programm mehrere Bezeichner oder Namen fur eine Speicheradresse, so werden
diese Ausrucke Aliase genannt [ASU86]. In C++ entstehen solche Mehrfach-Bezeichnungen fur Spei-
cheradressen durch Zeiger oder Referenzen, z.B. durch Speicherung, Zuweisung oder Ubergabe als
Parameter an Funktionen.
Um ein effizientes Programm zu erhalten, sind die Aliase vom Compiler an den Stellen aufzulosen, an
denen die referenzierten Speicherplatze den gleichen Wert enthalten. Dies sind also Falle, in denen uber
die Aliase nur gelesen und nicht geschrieben wird. Konnen diese Mehrfach-Referenzierungen vom Com-
piler nicht erkannt werden, so entsteht bei der Ausfuhrung des resultierenden Programms ein weniger
effizienter Code, denn das Datum im Speicher muss in beiden Fallen neu gelesen werden, obwohl es sich
nach dem ersten Lesen vielleicht noch im Cache befindet. So werden uberflussige Loads angestoßen,
welche bei haufigen Aufrufen die Performance merklich beeintrachtigen.
Der Compiler muss jedoch beim Erkennen und Auflosen der Aliase sehr konservativ vorgehen, um
keinen fehlerhaften Code zu erzeugen, der dann veranderte Daten nicht neu laden wurde. Aus der
grundsatzlichen Sicht konnte jedes Alias jeden Speicherbereich verandern, was aber keinerlei Opti-
mierungen zulassen wurde. Deshalb versucht der Compiler beim Ubersetzen mittels Datenflussanalyse
die Aliase zu bestimmen und, sofern sichergestellt ist, dass die Daten nicht verandert werden, diese
miteinander zu identifizieren.
2.4.4 Optimierung von Schleifen
Da bei gewohnlichen Programmen ein Großteil der Rechenzeit in Iterationsschleifen verbraucht wird,
ist die Optimierung von Schleifen unumganglich um performante Codes zu erzielen. Die generelle Ziel-
setzung ist dabei, die Zahl der auszufuhrenden Operationen zu reduzieren, sowie die benotigten Spei-
cherzugriffe moglichst cache-effizient zu gestalten. Nachfolgend findet sich eine Auswahl von wichtigen
Optimierungsmethoden, die je nach Problemstellung angewendet werden konnen [ASU86].
Neben der loop invariant code motion, bei der Codeteile, die sich nicht im Laufe der Schleifeniteration
verandern, vor oder nach die Schleife verlagert werden, werden dazu vor allem folgende Schleifentrans-
formationen vorgenommen.
16
KAPITEL 2. SPRACHGRUNDLAGEN UND PROGRAMMIERKONZEPTE
• Beim loop interchange, werden Iterationsschleifen vertauscht, um z.B. die Folge von Array-
durchlaufen zu andern. Dadurch kann in bestimmten Fallen eine bessere Nutzung der Caches
erreicht werden.
• Durch das Vereinigen von Schleifen (loop fusion) werden mehrere Schleifen in Eine zusammen-
gefasst, wodurch zusatzliche Kosten, wie Schleifenzahler oder Sprungbefehle, eingespart werden
konnen.
• Dem gegenuber kann auch das Aufspalten von Schleifen (loop fission), bei der eine Schleife in
mehrere aufgeteilt wird, zu einer besseren Ausnutzung der Caches fuhren bzw. eine Vektorisierung
erleichtern.
• Das cache blocking transformiert eine einfache in eine mehrfach geschachtelte Iterationsschlei-
fe, um so Arrays in Blockgroßen abarbeiten zu konnen, die der jeweiligen Große der Caches
entsprechen.
• Beim Entrollen von Schleifen (loop unrolling) werden die Schleifenrumpfe wiederholt ausgeschrie-
ben, um den Verwaltungsaufwand der Schleife zu verringern.
Wahrend manche Schleifentransformationen, wie etwa das Entrollen von Schleifen, durch den Com-
piler zur Ubersetzungszeit moglich sind, mussen andere oft explizit durch den Programmierer imple-
mentiert werden.
2.4.5 Kai C++-Compiler
Der Kai C++-Compiler wird gerade in Artikeln uber ET sehr haufig als derjenige Ubersetzer auf-
gefuhrt, mit dem die leistungsstarksten Programme erzielt wurden [BDQ97, LvG99]. Jedoch wurde
dieser Compiler von Intel ubernommen und die daraus entstandenen Versionen sind leider weniger
machtig.
Grundsatzlich funktionierte dieser Compiler u.a. wie ein Code-to-Code-Translator, der aus C++-
Programmen nach bestimmten High-Level-Optimierungen einen C-Code erzeugte, der dann mittels
gangigen C-Compilern ubersetzt werden konnte. Daraus ergaben sich sehr effiziente Programme, da
in C hardwarenahe Optimierungen leichter moglich sind als in C++, z.B. das Auflosen von Aliasen.
Außerdem konnten so gewisse Performance-Probleme von C++-Implementierungen leichter untersucht
werden, weil die Ergebnisse des Compilers als C-Code betrachtet werden konnten [BDQ97].
2.5 Zugrundeliegende Programmcodes
Prinzipiell konnen ET-Implementierungen in Verbindung mit den unterschiedlichsten Datenstruktu-
ren verwendet werden. Die klassische und weit verbreiteste Anwendung ist aber die Verwendung von
vektorbasierten Datenstrukturen.
Zum besseren Verstandnis und zur einheitlichen, kurzeren Darstellung der Programmiertechniken
die im Rahmen dieser Dissertation prasentiert werden, bauen die spateren Implementierungen auf
eine simple Vektorklasse auf. Eine minimale Version dieser Klasse zeigt Listing 2.1. Der Einfachheit
halber besteht diese Vektorklasse aus einem dynamischen Array vom Typ double. Stattdessen konnte
aber auch jeder andere Datentyp dafur gewahlt werden, der die gangigen mathematischen Operatoren
implementiert.
Neben dem Konstruktor, der ein dynamisches Array der angegebene Große erzeugt und dieses in-
itialisiert, hat die Klasse Vector auch einen Kopier-Konstruktor und einen Destruktor. Weiter zeigt die
Implementierung der Vektorklasse den uberladenen Zuweisungsoperator, um das Kopieren eines Vek-
tors mittels der mathematischen Notation a = b schreiben zu konnen. Durch die Methode size wird die
17
2.5. ZUGRUNDELIEGENDE PROGRAMMCODES
Große des Arrays zuganglich gemacht. Die ubliche Zugriffsfunktion mittels des Operators operator[ ],
wird hier lediglich als Zuweisung der Komponenten eingefuhrt. Als eigentliche Zugriffsfunktion dient
eine give-Funktion, welche die i-te Komponente des Vektors zuruck gibt. Diese Methode verandert das
Objekt nicht und ist deswegen als konstant deklariert.
Listing 2.1: Vektordatenstruktur, Code-Grundlage fur weitere Implementierungen.
c l a s s Vector
p r i v a t e :
i n t s i z e ;
double ∗ data ;
5 pub l i c :
Vector ( i n t n , double i n i t = 0 . ) : s i z e ( n )
data = new double [ s i z e ] ;
f o r ( i n t i n d e x = 0 ; i ndex < s i z e ; ++index )
da ta [ i ndex ] = i n i t ;
10
˜Vector ( )
de l e t e [ ] da ta ;
Vector ( const Vector& vec )
15 a s s e r t ( s i z e == vec . s i z e ( ) ) ;
f o r ( i n t i n d e x = 0 ; i ndex < s i z e ; ++index )
da ta [ i ndex ] = vec . g i v e ( i ndex ) ;
Vector& ope ra to r= ( const Vector& vec )
20 a s s e r t ( s i z e == vec . s i z e ( ) ) ;
f o r ( i n t i n d e x = 0 ; i ndex < s i z e ; ++index )
da ta [ i ndex ] = vec . g i v e ( i ndex ) ;
r e t u r n (∗ t h i s ) ;
25 i n t s i z e ( ) const r e t u r n s i z e ;
double& ope ra to r [ ] ( i n t i n d e x )
r e t u r n data [ i ndex ] ;
double g i v e ( i n t i n d e x ) const
30 r e t u r n data [ i ndex ] ;
double ∗ d a t a p o i n t e r ( ) const r e t u r n data ;
;
Auf dieser einfachen Datenstruktur werden im Laufe dieser Arbeit verschiedene ET-Varianten auf-
gebaut. Falls fur die prasentierten Techniken Veranderungen dieser Vektorklasse notig sind, so werden
nur diese angedeutet. Die restlichen Variablen und Methoden konnen einfach ubernommen werden, um
eine vollstandige Implementierung zu erhalten. Die Codefragmente der nachfolgenden Kapitel sind dar-
auf ausgelegt, alle definierten Schnittstellen exakt anzugeben. Dabei wird besonders auf die Ubergabe
von Objekten als konstante Referenz geachtet, wo immer dies moglich und sinnvoll ist. Auch werden
die inline -Deklarationen von Funktionen angegeben, wenn diese notig sind.
18
KAPITEL 3. KLASSISCHE ET
3 Klassische ET
Der intuitive Ansatz, das Uberladen von Operatoren in C++ zu nutzen, fuhrt oft zu Programmen, wel-
che nicht die gewunschte Effizienz erreichen. Fur kleine Datenstrukturen wurde dies dennoch mit Erfolg
eingesetzt, z.B. beim STL-Typ complex [Str00]. Doch der Leistungsverlust, der durch die Einfuhrung
der mathematischen Schnittstellen entsteht, wird deutlich, wenn man die Performance großer, arrayba-
sierter Datenstrukturen betrachtet. Vergleicht man diese mit Implementierungen per Hand, bei denen
direkt mit Datenarrays gearbeitet wird, so verschlechtert sich die Performance der Codes mit steigender
Anzahl der Operationen enorm. Diese bekannte Problematik fuhrte dazu, dass dem Operatoruberladen
in C++ gerade in performance-relevanten Bereichen wie etwa dem Hochleistungsrechnen wenig Beach-
tung geschenkt wurde. Dies anderte sich jedoch mit der Einfuhrung der ET-Technik, die eine effiziente
Realisierung des Operatoruberladens ermoglicht.
Im Folgenden werden zunachst die Probleme des traditionellen Uberladens von Operatoren betrach-
tet. Darauf aufbauend werden die ET-Implementierungstechnik erlautert, zunachst mit einigen Be-
merkungen zu Veldhuizens erster prasentierter Version. Zusatzlich werden noch gangige Varianten der
ET-Programmierung und bekannte Bibliotheken dargestellt.
3.1 Das traditionelle Uberladen von Operatoren
Um das Problemfeld bezuglich des Uberladens von Operatoren ubersichtlich zu halten, beschrankt sich
die nachfolgende Diskussion auf das Uberladen des Plusoperators fur die Addition von Vektoren aus
Listing 2.1.
Eine Implementierungsvariante, die ein C++-Compiler gut und einfach optimieren kann, ist sicherlich
ein Programmcode im C-Stil. D.h. im Falle einer komponentenweisen Addition von Vektoren werden
die Summen in einer for-Schleife fur die Eintrage der Datenarrays berechnet.
Vector a (N) , b (N) , c (N) , d (N) ;
. . .
f o r ( i n t i =0; i<N; ++i )
a [ i ] = b . g i v e ( i )+ c . g i v e ( i ) + d . g i v e ( i ) ;
Hierbei wird insbesondere auch bei der Addition beliebig vieler Vektoren nur ein Schleifendurchlauf
benotigt und es werden keinerlei temporare Vektoren angelegt. Der Nachteil ist jedoch, dass diese Pro-
grammierweise gerade fur kompliziertere Datenstrukturen als Vektoren leicht zu Implementierungsfeh-
lern fuhrt. Solche aufwandigeren Datenstrukturen entstehen beispielsweise beim numerischen Losen
von partiellen Differentialgleichungen, bei denen die Diskretisierungssterne bestimmte Vektorkompo-
nenten verknupfen. Diese Komponenten sind im Diskretisierungsgebiet Nachbarn, doch auf Grund
entsprechender Nummerierungen liegen diese meist uber den Vektoren verstreut, siehe auch Kapitel
10.1. Wird eine solche Problemstellung im C-Stil programmiert, konnen leicht schwer aufzulosende
Zugriffsfehler entstehen.
3.1.1 Unmittelbare Auswertung im Operator
Aufbauend auf der Vektorklasse aus Listing 2.1 wollen wir entsprechend uberladene Operatoren ein-
fuhren, um einfache und intuitive mathematische Schnittstellen zu schaffen. Dadurch ist ein Anwender
19
3.1. DAS TRADITIONELLE UBERLADEN VON OPERATOREN
nicht mehr gezwungen, die for-Schleife mit den komponentenweisen Operationen selbst zu implemen-
tieren. Der herkommliche Weg, die komponentenweise Summe zweier Vektoren mittels uberladenen
Operatoren zu realisieren, berechnet das Ergebnis der Addition unmittelbar im Operator. Dieser gibt
dann einen Ergebnisvektor zuruck, der die Summe der beiden Vektoren enthalt. Fur diese Berechnung
muss im Operator eine Iteration uber die Vektordaten durchlaufen werden, in welcher fur jede Kom-
ponente die Summe berechnet wird. Der Ergebnisvektor muss dabei jedesmal lokal erzeugt und bei der
Ruckgabe kopiert werden.
Vector ope ra to r+ ( const Vector& v1 , const Vector& v2 )
Vector tmp ( v1 . s i z e ( ) ) ;
f o r ( i n t i = 0 ; i < v1 . s i z e ( ) ; ++i )
tmp [ i ] = v1 [ i ] + v2 [ i ] ;
r e t u r n tmp ;
Um zu verstehen welche Probleme bei dieser Implementierung entstehen, wollen wir den folgenden
einfachen Anwendungscode betrachten. Ohne auf die Inhalte der Vektoren einzugehen, soll die Summe
von drei Vektoren berechnet und in einem vierten gespeichert werden.
a = b + c + d ;
Fur die Analyse wird die Arbeitsweise dieser Implementierung betrachtet, wobei wir annehmen, dass
alle unnotigen temporaren Objekte durch die Optimierung bereits eliminiert werden.
Zunachst wird die Summe von b und c berechnet, wofur innerhalb des Operators ein temporarer
Vektor erzeugt und mittels einer Iteration berechnet wird. Dieser temporare Vektor wird dann als Er-
gebnis der Operation in einer zweiten Addition mit d summiert. Dabei benotigen wir wiederum einen
temporaren Vektor und einen Schleifendurchlauf fur die Berechnung des Resultats. Das Ergebnis wird
dann an a zugewiesen, wofur wiederum eine Iteration durchgefuhrt wird. Insgesamt benotigen wir also
fur die Summation von drei Vektoren zwei temporare Vektoren, sowie drei Schleifendurchlaufe. Allge-
meiner braucht man mittels dieser Methode fur die Addition von n Vektoren n− 1 temporare Objekte
und n Iterationen. Wegen der wiederholten Allokation und dem anschließenden Loschen temporarer
Vektoren sowie den mehrfachen Schleifendurchlaufen sinkt die Performance dieser Implementierung
mit steigender Vektorgroße und steigender Operandenanzahl.
Betrachtet man Ausdrucke, bei denen der Vektor der linken Seite auch auf der rechten Seite auftritt
(z.B. a = a + b + c), kann ein temporarer Vektor und eine Iteration eingespart werden. Anstatt die
Addition von a explizit durchzufuhren, kann diese mit der Zuweisung in einem zusatzlichen operator+=
zusammengefasst werden (a += b + c). Diese Verbesserung kann aber lediglich in Sonderfallen ange-
wendet werden und lost nicht die eigentliche Problematik des traditionellen Operatoruberladens, d.h.
der sofortigen Berechnung der Operationen.
3.1.2 Ausdrucksbaume mittels abstrakter Klassen
Bei der Summation mehrerer Vektoren per Hand berechnet man jede Komponente des Ergebnisses
direkt aus der Summe der einzelnen Komponenten der Vektoren der rechten Seite, wobei keinerlei
temporare Vektoren und insgesamt nur eine einzige Iteration notig sind. Prinzipiell benutzt man fur die
Auswertung jeder Komponente den gesamten Ausdruck(sbaum) und wendet die jeweiligen Operationen
auf die i-ten Eintrage an. Um diesen Algorithmus zu implementieren muss man, anstatt die Operation
sofort auszufuhren, mittels der uberladenen Operatoren einen Ausdrucksbaum aufbauen, auf den die
Auswertung angewandt werden kann.
Diese Idee kann wie in Listing 3.1 implementiert werden, indem man eine abstrakte Klasse zur Kap-
selung der auftretenden Operationsklassen einfuhrt. Auf Grund der Vererbung muss die Komponenten-
funktion give jedoch als virtuell deklariert werden. Die Klassen, welche die Operationen beschreiben,
20
KAPITEL 3. KLASSISCHE ET
besitzen dann die implementierten Funktionen zum Berechnen der i-ten Komponente aus den gespei-
cherten Operanden. Da diese Funktionen nur einen sehr kleinen Funktionsrumpf haben und durch die
spate Bindung der virtuellen Funktionen zur Laufzeit ein großerer Overhead entsteht, resultieren aus
dieser Implementierung wiederum ineffiziente Programme. Genauer betrachtet, verschlechtert sich im
Allgemeinen die Performance bei dieser Programmierung noch im Vergleich zur vorher prasentierten
Berechnungsweise mittels temporarer Vektoren.
Listing 3.1: Implementierung eines Ausdrucksbaumes mittels abstrakter Klassen.
s t r u c t Exp r e s s i on
v i r t u a l double g i v e ( i n t i ) const = 0 ;
;
5 c l a s s Add : pub l i c Exp r e s s i on
p r i v a t e :
const Base∗ l ;
const Base∗ r ;
pub l i c :
10 Add( const Base∗ l , const Base∗ r ) : l ( l ) , r ( r )
double g i v e ( i n t i ) const
r e t u r n l −>g i v e ( i )+ r −>g i v e ( i ) ;
;
15 i n l i n e Add ope ra to r+(const Base& l , const Base& r )
r e t u r n Add(& l ,& r ) ;
c l a s s Vector : pub l i c Exp r e s s i on
. . .
20 ;
Allein der Aufbau eines Ausdrucksbaumes lost also nicht die Effizienzprobleme, die durch das Uber-
laden von Operatoren entstehen. Die Verwendung von virtuellen Funktionen mit einer spaten Bindung
zur Laufzeit und damit die fehlenden Inlining-Optimierungen fuhren zu keiner Leistungsverbesserung.
Eine effiziente Implementierung des Ausdrucksbaumes muss auf einer leistungsfahigen Technik basie-
ren, die alle Optimierungen zur Ubersetzungszeit ermoglicht.
3.2 Klassische ET-Implementierungen
Die Revolution bezuglich der großen Effizienzprobleme des Operatoruberladens in C++ wurde durch
die Einfuhrung von Expression Templates eingeleitet, zeitgleich prasentiert im Jahr 1995 von Veld-
huizen und Vandevoorde. Veldhuizen sah in seinem Artikel uber ET [Vel95] das Potenzial darin, die
Technik als performanten Ersatz fur Call-Back-Funktionen und als effizientes Operatoruberladen fur
arraybasierte Datenstrukturen einzusetzen. Dagegen prasentierte Vandevoorde ET als Variante, um
auf bestehende Klassen bzw. Container-Klassen (STL-Container valarray ) ein effizientes Uberladen von
mathematischen Operatoren aufzusetzen, und veroffentlichte die Idee in Online-Foren. Publiziert ist
die Implementierungsvariante unter anderem in [VJ03]. Die eigentliche Verbreitung der ET-Technik
fur das effiziente Berechnen numerischer Probleme leistete eine Veroffentlichung von Haney mit einer
Publikation in Computers in Physics [Han96], die im Wesentlichen auf den Artikel von Veldhuizen
aufbaut.
Wie der Name der Expression Templates (zu dt.: Ausdrucksschablonen, Ausdruckstemplates) bereits
verrat, werden bei dieser Technik Ausdrucke in templatisierte Objekte umgewandelt. Anders als bei Im-
plementierungen mit abstrakten Klassen und virtuellen Funktionen sind damit jederzeit die Typen der
Operationen bekannt. Dabei werden durch die uberladenen Operatoren wie vorher die Ausdrucksbaume
aufgebaut, in denen die Operationen als Template-Typen gespeichert sind. Die Auswertung findet erst
bei einer Zuweisung in einen neuen Vektor statt, bei der der Ausdruck dann komponentenweise in
21
3.2. KLASSISCHE ET-IMPLEMENTIERUNGEN
einer Schleife berechnet wird. Ihre Performance erreichen ET dann durch die Inline-Optimierungen des
C++-Compilers, welche durch die in den Templates enthaltenen Typinformationen moglich sind.
3.2.1 ET-Programmierung via Veldhuizen
Wahrend die Auswertungen von Ausdrucken mittels ET nahezu vollstandig optimiert werden, konnen
beim Aufbau der Ausdrucksbaume jedoch unerwartet ineffiziente Programmcodes entstehen. Dies ist
meist dann der Fall, wenn dabei mehrfach Objekte kopiert und neu angelegt werden mussen.
Wie bereits erwahnt, veroffentlichte Veldhuizen in seinem Artikel die ET fur die Losung zweier Pro-
bleme in C++. Zum einen beschrieb er die Technik als Verbesserung zu den von C geerbten und
teilweise ineffizienten Call-Back Funktionen. Hierbei handelt es sich vorwiegend um kleine Objekte,
deren Kopien beim Aufbau der Expression keines großen Aufwands bedurfen. Bei der zweiten prasen-
tierten Problemstellung, dem effizienten Uberladen der Operatoren fur arraybasierte Datenstrukturen,
musste Veldhuizen jedoch darauf achten, dass die Vektordaten nicht unnotig kopiert werden, da dies bei
großen Vektoren einen ineffizienten Aufbau der Ausdrucksbaume zur Folge gehabt hatte. Um diese Ko-
pien zu vermeiden, werden in der damals prasentierten Implementierung nur die Zeiger auf die Arrays
abgespeichert, anstatt der gesamten Vektorklasse. Dadurch werden lediglich die Zeiger kopiert bzw. die
wiederum kleinen Objekte der gekapselten Operationen. Mit dieser Variante schafft Veldhuizen zwar
einen sehr effizienten Code, schrankt diese Verwendung von ET aber auf arraybasierte Datenstrukturen
ein.
Zum Aufbau eines Ausdrucksbaumes werden in der veroffentlichten Implementierung, die im Anhang
A der vorliegenden Arbeit vollstandig prasentiert ist, sogenannte Operationsklassen definiert, welche
die Funktionsweise einer Operation reprasentieren. Sie enthalten die zugrunde liegenden Operanden
als Membervariablen, deren Typen mittels Templates spezifiziert sind. Der Template-Mechanismus ge-
neriert dann wahrend der Ubersetzung des Programms die notigen Instantiierungen, abhangig von den
auftretenden Ausdrucken. Die Operanden in den Operationsklassen sind als volle Objekte abgespei-
chert, und deshalb werden beim Kopieren einer Expression auch die gespeicherten Objekte kopiert. Die
eingeschrankte Schnittstelle fur die verschiedenen Ausdrucke wird mittels einer sogenannte Wrapper-
Klasse eingefuhrt. Diese hat prinzipiell nur die Funktion die entstandenen Ausdrucke zu kapseln, um
die Parameter der Operatoren auf diesen templatisierten Typ zu spezifizieren.
Veldhuizen stand bei seiner Implementierung vor dem Problem, dass es zu dem Zeitpunkt keine
Moglichkeit gab, Member-Templates zu definieren. Deshalb musste er den Umweg uber eine abstrakte
Zuweisungsklasse gehen, um den Typ des Ausdrucks zu fassen und den Zuweisungsoperator zu reali-
sieren. Die dazu benotigten virtuellen Funktionen konnen bei den Optimierungen des Compilers nicht
durch Inlining optimiert werden. Doch dieser Aufruf einer virtuellen Funktion betrifft nur den ersten,
außersten Aufruf der Auswertung und hat somit keinen großen Einfluss auf die Performance.
3.2.2 Verbreitete ET-Implementierungen
Um einen Vergleich zu den neu entwickelten, in Kapitel 5 und 6 prasentierten Implementierungen zu
haben, wird im Folgenden eine gangige ET-Variante diskutiert. Diese stutzt sich auf die Programmie-
rungen von Vandevoorde [VJ03], sowie die bereits etwas einfachere Version in [AG05].
Listing 3.2 zeigt eine gangige Implementierung von ET, wie sie in einigen verbreiteten Bibliotheken
verwendet wird. Hierbei dient die Wrapper-Klasse wiederum als gemeinsame Schnittstelle fur die Ope-
rationen. Doch anstatt jedesmal das durch dem Wrapper gekapselte Objekt in den Operationsklassen
zu speichern, werden hier Referenzen auf die Objekte abgespeichert. Mittels der Funktion rep werden
die eigentlichen Operationen den Konstruktoren der Operationsklassen ubergeben. Dieses Abspeichern
von Referenzen verhindert das wiederholte Kopieren von Objekten, besonders von den Vektoren. Trotz-
dem, und darauf wird in Kapitel 4 genauer eingegangen, muss in der Wrapper-Klasse ein volles Objekt
abgespeichert werden. Zusatzlich zu dieser Implementierung enthalt Listing 3.2 die benotigten vier
22
KAPITEL 3. KLASSISCHE ET
uberladenen Versionen des Plusoperators, der Addition von zwei Vektoren, Vektor und Expression,
Expression und Vektor, sowie der Summe zweier Expressions. Die dabei entstehenden Operationsklas-
sen mussen wiederum in der Wrapper-Klasse gekapselt werden. Um die dazu notige Konstruktion in
den Operatoren zu vereinfachen, fuhrt man einen typedef ein, der den Typ der Addition definiert.
Listing 3.2: Gangige Implementierungsvariante der ET-Technik.
template <c l a s s E>
c l a s s Expr
E e ;
pub l i c :
5 Expr ( const E& e ) : e ( e )
double g i v e ( i n t i ) const r e t u r n e . g i v e ( i ) ;
const E& rep ( ) const r e t u r n e ;
;
s t r u c t Plus
10 s t a t i c double app l y ( double a , double b ) r e t u r n a + b ;
;
template <c l a s s L , c l a s s OpTag , c l a s s R>
c l a s s B ina r y Exp r
const L& l ; const R& r ;
15 pub l i c :
B i na r y Exp r ( const L& l , const R& r ) : l ( l ) , r ( r )
double g i v e ( i n t i ) const
r e t u r n OpTag : : app l y ( l . g i v e ( i ) , r . g i v e ( i ) ) ;
20 ;
i n l i n e Expr<Bina ry Expr<Vector , Vector , Plus> >
ope ra to r+(const Vector& a , const Vector& b )
t ypede f Bina ry Expr<Vector , Vector , Plus> ExprT ;
r e t u r n Expr<ExprT>(ExprT ( a , b ) ) ;
25
template<c l a s s A>
i n l i n e Expr<Bina ry Expr<A, Vector , Plus> >
ope ra to r+(const Expr<A>& a , const Vector& b )
t ypede f Bina ry Expr<A, Vector , Plus> ExprT ;
30 r e t u r n Expr<ExprT>(ExprT ( a . r ep ( ) , b ) ) ;
template<c l a s s A>
i n l i n e Expr<Bina ry Expr<Vector ,A, Plus> >
ope ra to r+(const Vector& a , const Expr<A>& b)
35 t ypede f Bina ry Expr<Vector ,A, Plus> ExprT ;
r e t u r n Expr<ExprT>(ExprT ( a , b . r ep ( ) ) ) ;
template<c l a s s A, c l a s s B>
i n l i n e Expr<Bina ry Expr<A,B, Plus> >
40 ope ra to r+(const Expr<A>& a , const Expr<B>& b)
t ypede f Bina ry Expr<A,B, Plus> ExprT ;
r e t u r n Expr<ExprT>(ExprT ( a . r ep ( ) , b . r ep ( ) ) ) ;
Wie an diesen Programmcodes erkennbar ist, wird die Implementierung von ET schnell komplex und
aufwandig. Dabei kann die Programmierung der sich ahnelnden uberladenen Versionen der Operatoren
leicht zu komplizierten Fehlermeldungen fuhren. Daruber hinaus sind in den richtigen Klassen ganze
Objekte bzw. nur Referenzen abzuspeichern, da sonst bereits Objekte geloscht sind und damit nicht
mehr existieren, obwohl sie im Weiteren noch referenziert werden (siehe Abschnitt 4.3).
Prinzipiell kann diese Implementierung dadurch abgewandelt werden, indem die Wrapper-Klasse
Expr weggelassen, und direkt mit den Typen Binary Expr als spezifische Schnittstelle gearbeitet wird. Da-
durch verkomplizieren sich die Operatoren jedoch noch mehr, denn die Expressions besitzen somit drei
Template-Typen. Hier als Beispiel eine Version des Plusoperators mit der Operationsklasse Binary Expr
23
3.3. BEKANNTE ET BIBLIOTHEKEN
als Schnittstelle.
Listing 3.3: Variante eines uberladenen Operators ohne explizite Wrapper-Klasse.
template<c l a s s A1 , c l a s s A2 , c l a s s OpA , c l a s s B1 , c l a s s B2 , c l a s s OpB>
Bina ry Expr<Binary Exp<A1 , A2 ,OpA>, B ina ry Expr<B1 , B2 ,OpB>,Plus>
ope ra to r+(const Binary Exp<A1 , A2 ,OpA>& a ,
const Bina ry Expr<B1 ,B2 ,OpB>& b)
r e t u r n
Bina ry Expr<Binary Exp<A1 , A2 ,OpA>,
B ina ry Expr<B1 ,B2 ,OpB>, Plus >(a , b ) ;
Daruber hinaus mussen fur unare Operationen weitere Operationsklassen definiert werden. Da dann
drei mogliche Typen fur die Parameter der zu uberladenden Operatoren zur Verfugung stehen, sind
folglich je neun Versionen zu implementieren.
Dennoch existieren einfachere und kurzere Programmierungvarianten der ET-Technik, wie in Kapitel
5.3 dargestellt. Hier wird dann fur jeden Operator nur noch je eine uberladene Version eines Operators
benotigt.
3.3 Bekannte ET Bibliotheken
Nach der Einfuhrung von ET durch Veldhuizen und Vandevoorde, war es vor allem der Artikel von
Haney [Han96], welcher die ET-Implementierungstechnik fur wissenschaftliche Fragestellungen bekannt
machte. Daraufhin wurden einige effiziente Bibliotheken mittels ET entwickelt, die nachfolgend zusam-
mengefasst werden.
PETE
Die Portable Expression Templates Engine (PETE) dient dazu, Entwickler bei der Implementierung
von ET zu unterstutzen. Mittels der Bibliothek konnen die notigen Codes generiert werden, um die
vielen ahnlichen Versionen der uberladenen Operatoren nicht per Hand implementieren zu mussen.
Dazu werden die Blatter und Operationen des Ausdrucksbaumes angegeben, anhand derer – unter
anderem auch fur STL-Container – ET-Implementierungen erzeugt werden [HCKS98].
Blitz++
Die Software-Bibliothek Blitz++ implementiert effiziente Operationen fur Vektoren, mehrdimensionale
Arrays, sowie Matrizen. Bei der Entwicklung dieser Bibliothek wurden viele Konzepte der generischen
Programmierung mittels Templates eingefuhrt und angewandt. Besonders wichtig war den Programmie-
rern dabei, die Performance von FORTRAN Implementierungen zu erreichen oder sogar zu ubertreffen
[Vel01].
MTL, MET, TVMET
Die Matrix Template Library (MTL) und Matrix Expression Templates (MET) sind effiziente linea-
re Algebra Bibliotheken. Die Intention hierbei war, den wissenschaftlichen Softwareentwicklern leis-
tungsstarke und benutzerfreundliche Implementierungen fur die immer wieder benotigten Vektor- und
Matrix-Berechnungen zur Verfugung zu stellen [SL01, Mas01]. Wie wir spater noch genauer sehen
werden, erreichen normale ET bei kleinen Datenstrukturen nicht die gewunschte Performance von C-
Implementierungen. Aus diesem Grund wurde fur kleine Vektoren die Tiny Vector Matrix Expression
Templates (TVMET) eingefuhrt [Pet03]. Dabei kann durch die Angabe der Vektorgroße zur Uberset-
zungszeit durch Templates ein effizienterer Programmcode erreicht werden.
24
KAPITEL 3. KLASSISCHE ET
uBLAS
uBLAS [WK02] ist eine C++-Bibliothek, die mittels ET leistungsstarke Berechnungen und Losungs-
algorithmen der linearen Algebra durchfuhrt, die bereits in der FORTRAN-Bibliothek BLAS implemen-
tiert wurden [LHKK79]. Diese Entwicklung aus der C++-Bibliothek BOOST [AS01] bietet ebenfalls
ein effizientes Uberladen der gangigen Vektor- und Matrix-Operationen an. Daruber hinaus sind auch
verschiedene Normen oder unterschiedliche Typen von Matrizen, sowie effiziente Loser implementiert.
POOMA
Die spezialisierte Anwendung der ET-Technik fur die Losung partieller Differentialgleichungen wurde
in der Bibliothek POOMA (Parallel Object-Oriented Methods and Appliations) realisiert. Dabei wer-
den sehr spezifische Schnittstellen angeboten, um Diskretisierungen mittels der Finiten Differenzen
Methode sehr einfach und intuitiv programmieren zu konnen. Die zugrunde liegenden Datenarrays
sind auf das parallele Losen der numerischen Probleme ausgelegt. POOMA bietet dazu speziell die
zwei Hauptdatentypen Fields und Particles [Rum96].
ExPDE
Eine effiziente, benutzerfreundliche Bibliothek zum Losen von partiellen Differentialgleichungen mit-
tels der Finiten Elemente Methode wurde in ExPDE (Expression Templates for Partial Differential
Equations) implementiert [Pfl01]. Aufbauend auf ET unterstutzt die Bibliothek das einfache Program-
mieren von Differentialoperatoren auf verschiedenen Diskretisierungsgittern. Eine sehr intuitive und
problemnahe Formulierung der zugrunde liegenden partiellen Differentialgleichungen ermoglicht eine
schnelle und einfache Implementierung, wobei besonders auch die Programmierung passender Loser
unterstutzt wird. Durch Compiler-Switches kann automatisch zwischen einer seriellen oder parallelen
Version gewahlt werden.
Weitere ET Bibliotheken
Daruber hinaus gibt es noch eine Reihe weiterer C++-Bibliotheken, deren Implementierungen auf ET
basieren. Obige Darstellung konzentriert sich aber auf die Auswahl der fur die Anwendungen dieser
Dissertation wichtigen Programme. Prinzipiell finden solche ET-Bibliotheken gerade wegen ihrer effizi-
enten mathematischen Schnittstellen und ihrer FORTRAN-ahnlichen Performance große Anwendung
im wissenschaftlichen Rechnen und der numerischen Simulation. Daneben werden aber auch weiterhin
neue ET-basierende Programme entwickelt, die haufig aus komplizierten und nicht optimalen Imple-
mentierungen bestehen.
Deshalb wird im Weiteren die Technik der ET genauer untersucht und einfachere Varianten der
Methoden prasentiert, um das effiziente Uberladen von Operatoren in C++ intuitiver und kurzer
programmieren zu konnen.
25
3.3. BEKANNTE ET BIBLIOTHEKEN
26
KAPITEL 4. ANALYSE VON ET UND VERBESSERUNGSANSATZE
4 Analyse von ET-Programmen und
Verbesserungsansatze
Nach der Einfuhrung in die ET-Technik in Abschnitt 3.2.2 und der Zusammenfassung der wichtigsten
Bibliotheken, die auf dieser Methode basieren (Abschnitt 3.3), werden im vorliegenden Kapitel die
Wirkungsweise sowie die einzelnen Bestandteile solcher Implementierungen analysiert. Dazu werden
im Folgenden die ET-Programmierungen aus Abschnitt 3.2.2 zugrunde gelegt, welche aufbauend auf
der in Abschnitt 2.5 prasentierten Vektorklasse die Operatoren uberladt.
4.1 Effizienz der ET-Technik
Die Performance von ET kann nur durch entsprechende Optimierungen des C++-Compilers erreicht
werden. Anders als bei der zunachst prasentierten Programmierung von Ausdrucksbaumen mittels
abstrakten Klassen und virtuellen Funktionen, sind die Typen aller ET-Objekte bereits zur Uberset-
zungszeit bekannt. Deshalb kann der Compiler die Inlining-Optimierungen durchfuhren und so den
großen Overhead der wiederholten Aufrufe von kleinen Funktionen vermeiden. Zur genauen Analyse
dient als einfaches Beispiel die Optimierung der Vektortriade
a = b + c ∗ d ;
mit komponentenweiser Addition und Multiplikation. Die entsprechenden Operationsklassen und uber-
ladenen Operatoren aus Abschnitt 3.2.2 dienen nun dazu, die Vektortriade in einen Template-Aus-
drucksbaum zu kapseln. Dabei besitzt das ET-Objekt der rechten Seite (bezeichnet mit expr) den
Template-Typ:
Bina ry Expr<Vector ,
B ina ry Expr<Vector , Vector , Times> >, Plus> > expr
Dieses Template-Objekt expr dient als Ubergabe fur den Zuweisungsoperator der Vektorklasse. Dabei
wird wahrend der Auswertung fur jede Komponente die Funktion give aufgerufen, deren geschachtelten
Aufrufe mittels des Inlining aufgelost werden konnen. Diese sukzessive Inline-Optimierungen werden
im nachfolgenden Code vereinfacht dargestellt.
expr . g i v e ( i )
== Plus : : app l y ( expr . l . g i v e ( i ) , expr . r . g i v e ( i ) )
== expr . l . g i v e ( i ) + expr . r . g i v e ( i )
== expr . l . g i v e ( i ) + Times : : app l y ( expr . r . l . g i v e ( i ) , expr . r . r . g i v e ( i ) )
== expr . l . g i v e ( i ) + expr . r . l . g i v e ( i ) ∗ expr . r . r . g i v e ( i )
== b [ i ] + c [ i ] ∗ d [ i ]
Durch dieses sukzessive Auflosen des Inlinings kann der Compiler prinzipiell die verschachtelten
Template-Konstrukte auf den Programmcode reduzieren, den man per Hand ohne uberladene Opera-
toren schreiben wurde. Dies stellt lediglich eine exemplarische Folge der Optimierungen des Compilers
dar und soll nur zur einfachen Anschauung dienen.
Da die uberladenen Operatoren die Template-Objekte des Ausdrucksbaumes aufbauen, entsteht vor
allem fur kleine Vektoren ein durchaus bemerkbarer Overhead im Vergleich zur Implementierung per
Hand. Doch fur Vektoren, die nicht mehr in den Cache passen, beeintrachtigt das Bilden der Opera-
tionsobjekte die Performance nicht mehr wesentlich. Hier uberwiegen dann die Loads und Stores, die
27
4.2. KOMPONENTEN VON ET
benotigt werden, um die Daten in und aus den Cache zu bringen. In Kapitel 6.1.1 wird erortert, dass
selbst diese gangigen ET-Implementierungen durchaus noch Optimierungspotenzial besitzen. Dies be-
trifft hauptsachlich ein unvollstandiges Auflosen des Zeiger-Aliasing, was sich aber nur bei Ausdrucken
bemerkbar macht, in denen Vektoren wiederholt auftreten.
Nachfolgend werden die verschiedenen Bestandteile von ET-Programmen untersucht, sowie deren
Funktionsumfang und Nutzen zusammengestellt.
4.2 Komponenten von ET
Naturlich konnen die Implementierungen von ET beliebig komplex werden, so dass die Programmie-
rinhalte weit uber die hier aufgefuhrten Konzepte hinausgehen konnen. Im Wesentlichen teilt sich ein
ET-Code in vier untereinander verknupfte Einheiten auf, die jeder Variante zugrunde liegen:
• Eine oder mehrere Wrapper-Klassen, zum Erstellen von Schnittstellen, die nicht von den zugrunde
liegenden Ausdrucken abhangen (sie bilden einen Teil der Nicht-Terminale im Ausdrucksbaum),
• Datenstruktur(en) mit entsprechenden Zugriffs- und Zuweisungsmethoden bzw. Objekte funda-
mentaler Datentypen (diese Objekte bilden die Terminale),
• Operationsklassen, welche als Darstellung der jeweiligen Operation die Operanden speichern und
die Operation(en) durchfuhren (Non-Terminale im Ausdrucksbaum) und
• Creator Funktionen und die uberladenen Operatoren zum benutzerfreundlichen Aufbau der ET-
Objekte.
Im Folgenden werden diese einzelnen Komponenten untersucht und deren spezifischen Eigenschaften
herausgearbeitet. Dabei werden zur besseren Anschauung die Expressions als Ausdrucksbaume mit
Blattern und Knoten bzw. mit Terminalen und Nicht-Terminalen bezeichnet.
4.2.1 Wrapper-Klassen
Um keine allgemeinen Template-Parameter in den uberladenen Operatoren einsetzen zu mussen, be-
notigt man eine oder zumindest eine beschrankte Anzahl von Schnittstellen, welche die verschiedenen
Ausdrucke kapseln, die mittels der implementierten Operatoren gebildet werden konnen. Dazu wer-
den sog. Wrapper- oder Handle-Klasse verwendet, die lediglich dazu dienen, den Zugriff auf andere
Klasse zu kontrollieren oder um die Schnittstellen von bestehenden Klassen entsprechend anzupassen
[Str00]. Wahrend die Implementierung von Veldhuizen mit einem Wrapper auskommt, kann man in den
weiteren Entwicklungen von ET sehen, dass auch mehrere solcher Klassen zur Kapselung eingesetzt
werden konnen. Zum Beispiel kann es durchaus sinnvoll sein, bei Implementierungen fur Vektor- und
Matrix-Berechnungen separate Wrapper-Klassen fur Vektor- bzw. Matrix-Expressions einzufuhren, um
jederzeit sicherzustellen, welcher Art das Resultat sein wird. Dadurch kann eine fehlerhafte Verwendung
der ET-Implementierung vermieden werden.
Die Wrapper-Klassen werden prinzipiell nur als Schnittstelle fur die Operatoren benotigt, d.h. beim
Erzeugen eines neuen Knotens, mussen die abgespeicherten Operanden nicht mehr gekapselt sein. Der
Wrapper wird also nur als außerste Schicht des Ausdrucksbaumes gebraucht und im Inneren kann man
– wie in Listing 3.2.2 – darauf verzichten. Es wurde also fur die Programmierung von ET genugen,
den Wrapper immer um die Wurzel des Ausdrucksbaumes herumzubauen und bei der Abspeicherung
in Operationsklassen diese Kapselung wieder wegzulassen. Innerhalb des Baumes werden dann direkt
die Aste angesprochen, ohne Zwischeninstanz.
Abbildung 4.1 stellt exemplarisch die Konstruktion des Ausdrucksbaumes zur Vektortriade (b + c ∗ d)
dar. Hierbei wird die Wrapper-Klasse, die die Multiplikation (b ∗ c) der linken Seite umfasst, im Aus-
drucksbaum der rechten Seite weggelassen.
28
KAPITEL 4. ANALYSE VON ET UND VERBESSERUNGSANSATZE
Abbildung 4.1: Die graphische Darstellung des Ausdrucksbaumes zur Vektortriade, wobei die
Wrapper-Klassen der Operanden weggelassen werden.
Fur diese Speicherung der Operanden ohne Wrapper-Klasse werden zusatzliche Methoden definiert,
die das gekapselte Objekt zuruckgeben (Funktion rep in Listing 4.1). Dieses enthalt bereits die komplet-
te Information bzgl. des Ausdrucksbaumes und genugt als gespeichertes Element. Diese Verkurzung
des Ausdrucksbaumes bringt auch fur die anstehenden Inline-Optimierungen Vorteile, da weniger ge-
schachtelte Aufrufe durch den Compiler aufgelost werden mussen.
Listing 4.1: Methode zum Abspeichern der Operanden ohne Wrapper.
template <c l a s s E>
c l a s s Expr
p r i v a t e :
const E exp r ;
pub l i c :
Expr ( const E& expr ) : e x p r ( expr )
double g i v e ( i n t i ) const
e x p r . g i v e ( i ) ;
const Expr& rep ( ) const r e t u r n e x p r ;
;
Eine weitere Moglichkeit die Wrapper-Kapselung im Inneren des Ausdrucksbaumes wegzulassen,
besteht in der Anwendung der CRTP-Technik, siehe Abschnitt 2.3.6. Dabei werden die Operations-
klassen von der Wrapper-Klasse mit ihrem eigenen Klassentyp als Template-Parameter abgeleitet.
Dadurch kann ein Objekt der Basisklasse durch eine explizit durchgefuhrte Konvertierung in ein Ob-
jekt des abgeleiteten Typs umgewandelt werden. Weil hierbei der Typ der Ableitung bekannt ist, kann
man Methodenaufrufe zur Auswertung durch einen static cast an das entsprechende abgeleitete Objekt
weiterreichen. Insgesamt erspart man sich durch die Verwendung der CRTP-Technik die zusatzliche
Speicherung eines Objektes der Wrapperklasse. Außerdem mussen die Objekte nicht mehr explizit
durch die Wrapper-Klasse gekapselt werden, weil jede abgeleitete Klasse bereits durch die CRTP-
Technik vom Typ der Wrapper-Klasse ist. Dies wird in Kapitel 5 dazu genutzt werden, eine einfache
Implementierung der Operatoren zu erreichen.
template <c l a s s E>
s t r u c t Expr ;
template <c l a s s A, c l a s s B, c l a s s Op>
c l a s s B ina r y Exp r : pub l i c Expr<Bina ry Expr<A,B,Op> > . . . ;
Somit kann die Wrapper-Klasse – im Prinzip – als leere Klasse definiert werden. Dann mussten jedoch
vor jedem Aufruf von Methoden oder vor jedem Ubergeben an eine Methode die Objekte entsprechend
in ihren abgeleiteten Typ umwandelt werden. Diese Umwandlungen werden vom Compiler zwar wie
29
4.2. KOMPONENTEN VON ET
gewunscht optimiert, doch wird der Programmcode durch die vielen expliziten Typumwandlungen
unnotig kompliziert.
Deshalb werden in den prasentierten Weiterentwicklungen spezielle Konvertierungs-Operatoren in
der Wrapper-Klasse definiert, um die entsprechenden Typumwandlungen vom Compiler automatisch
durchfuhren zu lassen (Listing 4.2). Solche benutzerdefinierten Typumwandlungen sollten in Imple-
mentierungen nur sehr gezielt eingesetzt werden, um unnotige Fehlerquellen in den Programmen zu
vermeiden. Im Falle der Wrapper-Klasse macht dies jedoch Sinn, denn es soll nur die explizite Um-
formung des Basisklassen-Typs in den Typ der abgeleiteten Klasse automatisch durch den Compiler
durchgefuhrt werden. Da ein Ausdruck wahrend der Auswertung meist nicht verandert werden soll,
sondern lediglich mehrere const-Funktionsaufrufe ausgefuhrt werden, genugt es in den meisten Fallen,
einen Konvertierungsoperator fur die Umwandlung in eine konstante Referenz zu definieren. Bei der
Implementierung ist aber darauf zu achten, dass der neu eingefuhrte Cast nicht wieder einen static cast
auf die konstante Referenz aufruft, da dies zu einer Endlos-Rekursion fuhren kann. Stattdessen sollte
man intern einen Cast auf den Zeigern auf die Objekte durchfuhren und das umgewandelte Ergebnis
dereferenzieren. Listing 4.2 zeigt eine nach mit diesen Vorgaben implementierte Wrapper-Klasse.
Listing 4.2: Wrapper-Klasse mit explizitem Konvertierungsoperator.
template <c l a s s E>
s t r u c t Expr
ope ra to r const Expr& ( ) const
r e t u r n ∗ s t a t i c c a s t <const E∗>( t h i s ) ;
double g i v e ( i n t i ) const
s t a t i c c a s t <const E&>(∗ t h i s ) . g i v e ( i ) ;
;
Zusammenfassend kann man die notigen Funktionen der Wrapper-Klasse wie folgt definieren. Die
Wrapper-Klasse
• ist eine Handle-Klasse und dient nur als Schnittstelle,
• ubergibt Methodenaufrufe an die entsprechenden Objekte, und
• wird nur als außerste Schicht der Ausdrucke benotigt.
Prinzipiell kann man auf die Einfuhrung von Wrapper-Klassen ganz verzichten, wenn die Operatoren
ohne diese explizite Schnittstelle uberladen werden. Dies kann durch das Uberladen mittels allgemeiner
Template-Typen erfolgen, wobei dies leicht zu unerwunschten Mehrdeutigkeiten fuhren kann, siehe dazu
auch Kapitel 5.2. Daneben konnen auch die Operationsklassen als Wrapper benutzt werden (Kapitel
4.2.3), was aber die Anzahl der uberladenen Versionen je Operator erhoht, da meist mehr als eine
Operationsklasse existiert, siehe Abschnitt 3.2.2.
4.2.2 Grunddatenstrukturen
Die Grunddatenstrukturen beschreiben die Blatter (Terminale) in einem Ausdrucksbaum, was ahn-
lich bereits in der Bibliothek PETE zum Erzeugen der ET-Codes so gehandhabt wurde [HCKS98].
Allgemein betrachtet konnen ET fur ganz unterschiedliche Datenstrukturen verwendet werden. Im
Folgenden werden verschiedenartige Varianten diskutiert, sowie die dazu notigen Anderungen in der
Implementierung erlautert.
In seinem bereits erwahnten Artikel uber ET betrachtete Veldhuizen [Vel95] die Verwendung der
Technik als Ersatz fur Call-Back-Funktionen und zum effizienten Uberladen von Operatoren von array-
basierten Datenstrukturen. Call-Back-Funktionen, die Ubergabe von Funktionszeigern als Parameter,
30
KAPITEL 4. ANALYSE VON ET UND VERBESSERUNGSANSATZE
sind eine Technik die von C ubernommen wurde. Dadurch war es unter anderem moglich, Sortieralgo-
rithmen mit benutzerdefinierter Sortierung oder numerische Integrationen fur anwendereigene Funktio-
nen zu definieren. Die Effizienzprobleme von Call-Back-Funktionen entstehen hauptsachlich durch den
wiederholten, nicht optimierbaren Aufruf von Funktionen mit faktisch kleinem Funktionsrumpf. Bei
Implementierungen mittels ET konnen durch entsprechend uberladene Operatoren ebenfalls Sortiervor-
schriften und Funktionen erfasst werden. Die zugrunde liegenden Datenstrukturen sind hierbei Klassen
ohne großen Dateninhalt, sie dienen lediglich als Platzhalter fur die Erstellung der Funktionsformeln.
Stattdessen wird die damit konstruierte ET-Formel fur verschiedene eingesetzte Daten ausgewertet.
Das nachfolgende Listing zeigt beispielsweise den Aufruf fur eine numerische Integration der Funktion
3x2 +√
x + 1−2 auf dem Intervall [0, 3]. Zur approximativen Berechnung des Integrals muss die Funk-
tion an bestimmten Stutzstellen ausgewertet werden. Doch die auftretenden ET-Ausdrucke selbst sind
nur kleine Objekte, die keinen großen Kopieraufwand erzeugen.
double r e s u l t =
i n t e g r a t e ( 0 , 3 ,
3 ∗ x (2) + Sqr t ( x (1) + 1 ) − 2 ) ;
Dem entgegen steht die Verwendung von ET fur Vektorberechnungen, bei der im Allgemeinen mit
großen Objekten gearbeitet werden muss. Hierbei werden Datenstrukturen mit dynamischen Arrays
als große Objekte bezeichnet, obwohl intern eigentlich nur der Zeiger zur Klasse gehort. Beim Erstellen
einer tiefen Kopie wird auch das dynamische Datenfeld kopiert. D.h. es ist darauf zu achten, dass
bei der Programmierung zum Aufbau des Ausdrucksbaumes diese Objekte nicht oder zumindest nicht
unnotig kopiert werden. Deshalb muss speziell bei Vektoren als Operanden sichergestellt sein, dass
diese nur als Referenzen und nicht als volle Objekte abgespeichert werden.
Prinzipiell konnen Implementierungen mittels ET aber fur Datenstrukturen beliebiger Große an-
gewendet werden. Wie wir in Kapitel 6.1.1 sehen werden, entsteht aber bei Zeigervariablen in den
Basis-Datenstrukturen ein Aliasing, das bei wiederholtem Auftreten in einer Expression nicht auf-
gelost werden kann. Deshalb sollten Zeiger und dynamische Arrays – wo moglich – vermieden werden,
auch wenn spater Methoden prasentiert werden die dazu dienen, den Compiler dabei zu unterstutzen
diese Aliasing-Probleme aufzulosen (siehe Kapitel 6.2).
Um in Kombination mit einer ET-Programmierung zu funktionieren, mussen Teile der Datenstruktur
entsprechend angepasst werden. Da bei herkommlichen Implementierungen die Basis-Datenstrukturen
nicht von der Wrapper-Klasse gekapselt sind, mussen fur alle Schnittstellen zusatzliche uberladene
Versionen eingefuhrt werden. Als Vereinfachung konnen diese wiederkehrenden verschiedenen Parame-
terlisten mittels Makros einmal allgemein definiert werden, und diese Varianten werden dann durch
den Precompiler erzeugt. Zum einen fuhren Makros aber zu schwerer verstandlichen und wartbaren
Programmcodes. Zum anderen kann man daraus resultierende Fehlermeldungen schwieriger nachvoll-
ziehen, da sie sich auf die bereits expandierten Makros beziehen, nicht auf den ursprunglichen Code.
Eine kurzere und verstandlichere Implementierung erhalt man, indem die zugrunde liegenden Da-
tenstrukturen, wie bereits bei den Wrapper-Klassen angedeutet, ebenfalls als Ableitung vom Wrapper
definiert werden. Um diese Kapselung nicht explizit durchfuhren zu mussen, kann an dieser Stelle
die CRTP-Technik angewandt werden. Die Datenstruktur erbt von der Wrapper-Klasse mit dem ei-
genen Typ als Template-Parameter. Dadurch kann automatisch jeder Vector in den entsprechenden
Expr<Vector> konvertiert werden. Als Resultat benotigt man keine zusatzlichen uberladenen Versionen
der Operatoren fur die Grunddatentypen mehr, da diese nun als Expressions behandelt werden und so
in die bereits definierten Operatoren passen.
c l a s s Vector : pub l i c Expr<Vector >
. . .
;
Als Verbindung der Grunddatenstruktur(en) und den entstehenden ET-Ausdrucken muss eine Aus-
wertung der Ausdrucksbaume implementiert werden. Dies geschieht meistens in einem Zuweisungsope-
31
4.2. KOMPONENTEN VON ET
rator operator=, kann aber auch in einer anderen (Member-)Funktion des Ergebnisdatentyps geschehen.
Die entsprechende templatisierte Methode hat einen Parameter vom Typ Expr<E>, dessen Auswer-
tungsfunktion aufgerufen wird. Arbeiten die Wrapper-Klassen mit CRTP und besitzen selbst keine
Auswertungsfunktion, so mussen die durch Wrapper gekapselten Objekte vor dem Aufruf noch in den
entsprechenden abgeleiteten Typ E umgewandelt werden.
c l a s s Vector : pub l i c Expr<Vector >
. . .
template <c l a s s E>
vo id ope ra to r=(const Expr<E>& e )
f o r ( i n t i = 0 ; i < s i z e ; ++i )
da ta [ i ] = s t a t i c c a s t <const E&>(e ) . g i v e ( i ) ;
;
Zusatzlich wird noch die Anwendung von ET fur bereits bestehende Datenstrukturen diskutiert,
ohne dabei die Implementierung der existierenden Klasse verandern zu mussen. Als erste Moglichkeit
kann man eine Handle-Klasse definieren, die von der bestehenden Datenstruktur erbt. Dadurch wird die
volle Funktionalitat auf die neue Klasse ubertragen [Fur97]. Es mussen lediglich eigene Konstruktoren,
Kopier-Konstruktoren und Zuweisungs- bzw. Zugriffsoperatoren eingefuhrt werden, welche die entspre-
chenden Daten an die eigentliche Klasse weiterreichen. Auf diese so abgeleiteten Handle-Klassen kann
dann eine Implementierung mittels ET aufgebaut werden. In einer Anwendung wird dann naturlich
nur die neue Klasse verwendet, die sich aber in der Funktionalitat nicht von der ursprunglichen unter-
scheidet und mit einer ET-Programmierung funktioniert.
s t r u c t Vector : pub l i c s t d : : v ec to r <double >, pub l i c Expr<Vector >
Vector ( i n t s i z e ) : s t d : : v ec to r <double >( s i z e )
. . .
;
Soll jedoch weiterhin mit der existierenden Klasse gearbeitet werden, so besteht die Moglichkeit, in
der Wrapper-Klasse des ET-Programms einen Konvertierungsoperator einzufuhren, der ein Expression-
Objekt in den Typ der bestehenden Klasse umwandelt. Dazu benotigt man aber intern ein tem-
porares Objekt bei der Auswertung, sowie einen bereits implementierten Kopier-Konstruktor bzw.
Zuweisungsoperator in der Basis-Datenstruktur. Daruber hinaus sind die Operatoren wieder fur alle
Kombinationen zwischen Ausdrucken und der bestehenden Grunddatenstruktur zu defnieren. Doch
mit der entsprechenden ET-Implementierung konnen dann fur die bestehende Datenstruktur wie
ublich Ausdrucksbaume aufgebaut und berechnet werden. Das nachfolgende Listing zeigt beispiels-
weise eine entsprechende Implementierung der Wrapper-Klasse fur die Verwendung mit STL-Vektoren
(std :: vector<double>).
template<c l a s s A>
s t r u c t Expr
ope ra to r const A& () const
r e t u r n ∗ s t a t i c c a s t <const A∗>( t h i s ) ;
ope ra to r s t d : : v ec to r <double> ( ) const
const A& a (∗ t h i s ) ;
i n t s i z e = a . s i z e ( ) ;
s t d : : v ec to r <double> tmp ( s i z e ) ;
f o r ( i n t i =0; i < s i z e ; ++i )
tmp [ i ] = a [ i ] ;
r e t u r n tmp ;
;
So analysiert ergibt eine große Menge von Datenstrukturen, bei denen es sinnvoll ist die Operatoren
oder entsprechenden Funktionen mittels ET-Implementierungen zu realsisieren.
32
KAPITEL 4. ANALYSE VON ET UND VERBESSERUNGSANSATZE
4.2.3 Operationsklassen
Durch die Operationsklassen werden die anwendbaren Operationen sowie die jeweiligen Operanden
gekapselt. Der Typ der Operanden – und das fuhrt wie bereits erwahnt zur Effizienz der ET – wird
in den Template-Parametern der Operationsklassen gespeichert, ist damit also zur Ubersetzungszeit
bekannt. In C++ gibt es nur unare und binare Operatoren, die uberladen werden konnen. Doch prinzi-
piell kann es auch sinnvoll sein, Operationen mittels Funktionen zu definieren, die auch drei oder mehr
Operanden kombinieren konnen (siehe Abschnitt 12.2).
Die Operationsklassen beschreiben die Knoten, also Nicht-Terminale im Ausdrucksbaum, an dem
ein oder mehrere Terminale oder Nicht-Terminale hangen. Neben dem Konstruktoraufruf mit den
jeweiligen Operanden, enthalten die Operationsklassen Auswertungsfunktionen die an jedem Knoten
im Ausdrucksbaum das Resultat aus den Ergebnissen der Operanden berechnen.
Im Wesentlichen existieren zwei Implementierungsansatze fur die Operationsklassen. Die erste Vari-
ante definiert fur unare und binare Operationen je eine allgemeine Operationsklasse mit einem zusatz-
lichen Template-Parameter (siehe Binary Expr in Listing 4.3), der die eigentliche Operation beschreibt.
Diese binare Operation wird durch eine Klasse mit einer statischen Funktion beschrieben, welche die
eigentliche Berechnung ausfuhrt (Plus in Listing 4.3). Mittels des Inlinings fur in-class definierte Metho-
den werden diese Funktionsaufrufe optimiert. Die Operationsklasse fur binare Operatoren stellt somit
nur eine Datenstruktur zum Speichern von Operanden und Ausfuhren einer binaren Operation dar.
Listing 4.3: ET-Programmiervariante mit allgemeinen Operationsklassen.
s t r u c t Plus
s t a t i c double app l y ( double l , double r )
r e t u r n l + r ;
;
template <c l a s s L , c l a s s R, c l a s s Op>
c l a s s B ina r y Exp r
p r i v a t e :
const L& l ;
const R& r ;
pub l i c :
B i na r y Exp r ( const L& l , const R& r ) : l ( l ) , r ( r )
double g i v e ( i n t i ) const
Op : : app l y ( l . g i v e ( i ) , r . g i v e ( i ) ) ;
;
Diese Implementierungsvariante bietet den Vorteil, dass weniger Programmcode geschrieben werden
muss, da fur weitere binare Operationen bereits die Operationsklasse vorhanden ist, und nur noch die
Operation uber die Klasse mit einer statischen Methode definiert wird. Da durch diese allgemeinen
Operationsklassen bereits gewisse Schnittstellen definiert sind, benotigt eine solche Implementierung
keine zusatzliche Wrapper-Klasse mehr. In diesem Fall konnen die Operanden als Referenzen abgespei-
chert werden, da beim Aufbau einer Expression keine lokalen Objekte erstellt werden. Ein Nachteil
dieser Version ist sicherlich die etwas komplizierte und schwerer lesbare Programmierung, bei der die
eigentliche Operation erst aus den Template-Parametern ersichtlich ist. Daruber hinaus mussen fur eine
Implementierung ohne Wrapper neun oder mehr uberladene Versionen jedes Operators programmiert
werden.
Im zweiten Ansatz zur Implementierung der Operationsklassen wird fur jede Operation eine eigene
Klasse mit der entsprechenden Datenstruktur eingefuhrt, siehe Listing 4.4. Dadurch erhalt man einen
ubersichtlicheren aber etwas langeren Programmcode. Daruber hinaus kann hier nicht einfach auf die
Wrapper-Klasse verzichtet werden, da man die Operatoren sonst fur jede Kombination von Operations-
objekten uberladen musste. Der entscheidende Vorteil dieser Variante ist jedoch die bessere Lesbarkeit,
weshalb die weiteren Programmcodes nach dieser Variante prasentiert werden.
33
4.2. KOMPONENTEN VON ET
Listing 4.4: ET-Programmcode mit separaten Operationsklassen.
template <c l a s s L , c l a s s R>
c l a s s Add i t i on
p r i v a t e :
const L& l ;
const R& r ;
pub l i c :
Add i t i on ( const L& l , const R& r ) : l ( l ) , r ( r )
double g i v e ( i n t i ) const l . g i v e ( i ) + r . g i v e ( i ) ;
;
Betrachtet man die Laufzeit der beiden Implementierungen, so lassen sich keine wesentlichen Un-
terschiede feststellen, je nach verwendetem Compiler konnen beide Versionen etwas schneller oder
langsamer laufen. Jedoch konnen sich fur die unterschiedlichen Versionen durchaus verschiedene Uber-
setzungszeiten ergeben (Kapitel 8).
Um die Operationsklassen nicht mehr explizit mittels eines Wrappers kapseln zu mussen, kann man
wie bei den Grunddatenstrukturen die Klassen mittels der CRTP-Technik von der Wrapper-Klasse
ableiten. Dann besitzen die Operationsklassen durch die Konvertierung in den Basis-Typ ebenfalls den
gekapselten Typ.
Listing 4.5: Ableitung der Operationsklassen vom Wrapper mittels CRTP.
template <c l a s s L , c l a s s R>
c l a s s Add i t i on : pub l i c Expr<Add i t i on <A,B> > . . .
4.2.4 Creator Funktionen und Uberladene Operatoren
Das Uberladen der Operatoren mit den ET soll dazu dienen, effiziente und benutzerfreundliche Biblio-
theken zu schreiben, weshalb es fur eine einfache Handhabung notig ist, die komplizierten geschachtelten
Template-Klassen vor dem Benutzer zu verbergen. Dazu nutzt man die Moglichkeit in C++ templa-
tisierte Funktionen bzw. Operatoren zu definieren, die Aufrufe ohne explizite Angabe der Templates
erlauben, wenn diese bereits durch die Argumentlisten eindeutig bestimmt sind (type deduction, siehe
Abschnitt 2.3.1).
Listing 4.6: Vektor-Exponentialfunktion mit Creator-Funktion.
template <c l a s s A>
c l a s s Exponen t i a l : pub l i c Expr<A>
const A& a ;
pub l i c :
E x ponen t i a l ( const A& a ) : a ( a )
double g i v e ( i n t i ) const
r e t u r n s t d : : exp ( a . g i v e ( i ) ) ;
;
template <c l a s s A>
Exponent i a l <A>
i n l i n e Exp ( const Expr<A>& a )
r e t u r n Exponent i a l <A>(a ) ;
Mit solchen Creator Funktionen kann man einfache Schnittstellen zur Verfugung stellen und aus be-
stehenden ET-Konstrukten weitere erzeugen, ohne beim Aufruf deren genaue Template-Typen angeben
zu mussen. Durch die Deklaration als templatisierte Funktion generiert der Compiler die benotigten
Instanzen zur Ubersetzungszeit. Listing 4.6 beschreibt als Beispiel die komponentenweise Anwendung
der Exponentialfunktion auf Vektoren. Die Creator Funktion Exp dient dabei zur einfachen Erzeugung
eines entsprechenden ET-Objektes.
34
KAPITEL 4. ANALYSE VON ET UND VERBESSERUNGSANSATZE
Da der Funktionsrumpf solcher Creator Funktionen meist klein ist – es wird meist ja nur ein ent-
sprechender Konstruktor aufgerufen – werden diese als inline deklariert, um diesen zusatzlichen Funk-
tionsaufruf zu optimieren. Solche Funktionen zum Erstellen einer Expression finden in allen Teilen des
Programms Anwendung, wo keine passenden Operatoren uberladbar sind, z.B. Sinus-, Cosinus- oder
Exponentialfunktionen.
Ursprunglicher Initiator fur ET sind neben den Creator Funktionen die uberladenen Operatoren zum
Verknupfen von ET-Objekten. Ebenso wie die Creator Funktionen benotigen die uberladenen Versionen
der Operatoren keine explizite Spezifizierung der verwendeten Template-Typen. Diese generiert sich
der Compiler aus den Parameterlisten selbst. Aus diesem Grund konnen die Operationen in einem
Anwendungscode einfach sukzessive nacheinander geschrieben werden, der Compiler erzeugt daraus
die notigen Instantiierungen.
Weiter man durch die Einfuhrungen von Template-Spezialisierungen fur die uberladenen Operatoren
oder Creator Funktionen erreichen, dass fur bestimmte Templates besondere Objekte erzeugt werden
(siehe Abschnitt 7.4).
Die Implementierung der Operatoren ohne die Benutzung temporarer Objekte ist fur die Abspeiche-
rung der Operanden in den Operationsklassen als Referenzen essentiell. Denn nur wenn beim Erstellen
der Expression im uberladenen Operator keine lokalen Objekte erzeugt werden, bzw. ohne weitere
Abhangigkeiten, zeigen die gepeicherten Referenzen auf noch existierende Objekte.
4.3 Lebensdauer von ET-Objekten
In den bereits prasentierten Versionen der Implementierungen von ET werden die Operanden der Ope-
rationsklassen entweder als volle Memberobjekte oder als Referenzen auf bereits existierende Objekte
gespeichert. Je nach Programmierweise sind beide Methoden anzuwenden, aber nur unter genauer
Berucksichtigung der Lebensdauer der ET-Objekte.
Betrachten wir eine Implementierung, die durchweg volle Objekte als Membervariablen speichert.
Dies ist in den herkommlichen Versionen der ET mit expliziter Wrapper-Klasse notig, da beim Erzeugen
eines Ergebnistyps ein Operationsklassenobjekt (ExprT(a,b)) lokal erzeugt wird und in der Wrapper-
Klasse gespeichert werden soll.
template <c l a s s A, c l a s s B>
i n l i n e Expr<Bina ry Expr<Expr<A>,Expr<B>,Plus> >
ope ra to r+ ( const Expr<A>& a , const Expr<B>& b)
t ypede f Bina ry Expr<Expr<A>,Expr<B>,Plus> ExprT ;
r e t u r n Expr<ExprT>(ExprT ( a , b ) ) ;
Hierzu muss also das lokale Objekt ExprT(a,b) kopiert werden. Da dieses Objekt nach Beenden des
operator+ geloscht wird, wurde eine gespeicherte Referenz auf ein nicht mehr existentes Objekt zeigen.
Also ist es notig, dass sowohl in der Wrapper-Klasse, als auch in den Operationsklassen volle Objekte
verwendet werden. Speichert man jedoch auch auf der untersten Ebene die Vektoren als volle Objekte
ab, so werden beim Aufbau des Ausdrucks in Templates die auftretenden Vektoren jedesmal kopiert.
Offensichtlich fuhrt dies besonders bei großen Vektoren zu einem sehr ineffizientem Aufbau der ET.
Um dieses unnotige Kopieren der Vektoren zu umgehen, gibt es verschiedene Losungsansatze.
• Veldhuizen [Vel95] arbeitet in seiner Implementierung der ET mit der Abspeicherung der zu-
grunde liegenden Zeiger anstatt der Vektoren. Diese Zeiger werden beim Aufbau der Expressions
mittels der uberladenen Operatoren zwar kopiert, aber nicht das Array auf das sie zeigen. Diese
Programmierung umgeht zwar das ineffiziente Kopieren, aber sie ist beschrankt auf array-basierte
Datenstrukturen. Außerdem erhalt man keine durchgangige Kapselung der Daten, da die Zeiger
auf die Datenarrays gesondert vom Rest der Vektorobjekte benutzt werden.
35
4.3. LEBENSDAUER VON ET-OBJEKTEN
• Der zweite Ansatz [VJ03] konzentriert sich darauf sicherzustellen, dass die Blatter der entstehen-
den Ausdrucksbaume nur als Referenzen gespeichert werden. Die Vektoren bestehen bereits vor
dem Aufbau der Expression und werden auch noch nach Beendigung der Berechnungen existieren.
Also konnen diese Objekte per Referenz gespeichert werden, wobei diese Referenz auch kopiert
werden kann, da das Objekt ja nicht geloscht wird. Die explizite Speicherung der Vektoren als
Referenzen innerhalb einer Expression kann mittels einer zusatzlichen Wrapper-Klasse fur die
Vektoren erreicht werden, durch geeignete Spezialisierungen der Operationsklassen oder spezielle
Versionen der uberladenen Operatoren. Dies erfordert aber eine weitere Indirektion beim Aufbau
der ET bzw. einen zusatzlichen Programmieraufwand beim Einfuhren der Spezialisierungen der
Operationsklassen.
• Verzichtet man auf eine explizite Wrapper-Klasse fur die Expressions, so konnen die uberladenen
Operatoren auf eine Weise implementiert werden, die ohne die Erzeugung von zusatzlichen loka-
len Objekten auskommen. Da dann keine gesonderten Kopien der ET-Objekte mehr durchgefuhrt
werden mussen, konnen durchgangig in den Operationsklassen Referenzen auf die Objekte ab-
gespeichert werden. Der Verzicht auf die Wrapper-Klasse benotigt aber – wie bereits erwahnt –
zusatzlichen Implementierungsaufwand, da hier mehrere Versionen von jedem uberladenen Ope-
ratoren definiert werden mussen. Diese Vielzahl an uberladenen Operatoren kann vermieden
werden, wenn die Operatoren durch allgemeine Template-Typen definiert werden, siehe Kapitel
5.2.
Wie im nachfolgenden Kapitel 5 dargestellt wird, ist es durchaus moglich Versionen von ET zu im-
plementieren, die nur mit Referenzen arbeiten, eine Wrapper-Klasse besitzen und mit nur je einer
uberladenen Operatorversion auskommen.
36
Teil II
Weiterfuhrende Programmierung von
Expression Templates
KAPITEL 5. EINFACHE ET-IMPLEMENTIERUNG
5 Einfache ET-Implementierung
5.1 Komplexitat der Implementierung
Wie an dem Beispiel der gangigen ET-Programmierung aus Abschnitt 3.2.2 bereits ersichtlich wird,
erweist sich die Implementierung der Technik als durchaus anspruchsvoll. Denn es mussen fur jeden
Operatortyp mehrere sich ahnelnde uberladene Versionen eingefuhrt werden, was den resultierenden
Code umfangreich macht. So sind bei Implementierungen mit einer Wrapper-Klasse vier uberladene
Versionen je Operator zu definieren, bei Varianten ohne explizite Wrapper-Klasse mindestens neun.
Diese vielen Operatordefinitionen konnen prinzipiell durch die Implementierung der Operatoren mit
allgemeinen Templates vermieden werden, jedoch konnen dadurch leicht fehleranfallige Programme
entstehen. Zusatzlich mussen die Membervariablen in den Operationsklassen in korrekter Weise als
Referenzen bzw. ganze Objekte abgespeichert werden. Bei falsch deklarierten Membervariablen konnen
entweder ineffiziente Implementierungen oder auch nicht funktionierende Codes entstehen. Neben den
Schwierigkeiten der Programmierung, die leicht zu unubersichtlichen Fehlermeldungen fuhrt, berei-
tet auch die schlechte Testbarkeit der Implementierungen Probleme. Da templatisierte Klassen und
(Member-)Funktionen nur dann vom Compiler instantiiert werden, wenn sie im weiteren Verlauf des
Programms benotigt werden, konnen bestimmte Fehler einer ET-Implementierung erst durch entspre-
chende Beispiele aufgedeckt werden.
Aus diesen Grunden ist es von großem Interesse uber einfachere Techniken der ET-Implementierung
zu verfugen, die kurzer und weniger fehleranfallig sind als die herkommlichen Programmiervarianten.
Dafur werden in diesem Kapitel drei einfache Strategien prasentiert, die eine ubersichtliche und da-
mit intuitivere Programmierung der ET ermoglichen. Zuerst wird eine einfache Variante diskutiert, die
ohne die Verwendung einer Wrapper-Klasse funktioniert, dabei aber in bestimmten Fallen zu Mehrdeu-
tigkeiten beim Instantiieren des Compilers fuhrt. Als zweites wird die Vererbung zur Ubersetzungszeit
genutzt, um eine einfache und typsichere ET-Programmierung zu realisieren. Darauf aufbauend enthalt
Abschnitt 5.4 eine Implementierung zur Abspeicherung von ET-Objekten.
5.2 Namespace-gekapselte ET-Implementierungen
Die eigentliche Komplexitat bei der Programmierung von ET entsteht erst durch die Einfuhrung und
Verwendung von Wrapper-Klassen zur Definition entsprechender Schnittstellen. Dadurch mussen die
entstehenden Operationsklassen in den Operatoren gekapselt, sowie mehrfach uberladen werden. Da die
Schnittstellen der Operatoren auch mit allgemeinen Template-Parametern definiert werden konnen, ist
es durchaus moglich eine Variante der ET-Technik zu implementieren, die ohne jegliche Wrapper-Klasse
auskommt [AG05].
Listing 5.1 zeigt den Programmcode einer entsprechenden ET-Version ohne Wrapper-Klasse. Dazu
gehoren die Operationsklassen, in denen die Operanden als Referenzen abgespeichert werden konnen,
da beim Aufbau einer Expression keine lokalen Objekte benotigt werden. Als Argumente besitzen die
uberladenen Operatoren dann allgemeine Template-Parameter und erzeugen wiederum die entsprechen-
den Objekte der Operationsklassen. In der Vektorklasse ist der Zuweisungsoperator ebenfalls mit einem
allgemeinen Template-Typen uberladen, da ohne Wrapper-Klasse der Typ des Ausdrucksbaumes jede
Operationsklasse sein konnte. Um die Verwendung der uberladenen Operatoren etwas einzuschranken,
wird die gesamte Implementierung in einen Namensbereich myExpr einigebettet. Dadurch kann erreicht
39
5.2. NAMESPACE-GEKAPSELTE ET-IMPLEMENTIERUNGEN
werden, dass die darin definierten uberladenen Versionen der Operatoren nur fur Objekte aus dem
Namensbereich verwendet werden.
Listing 5.1: Namespace-gekapselte ET-Implementierung.
namespace myExpr
template <c l a s s L , c l a s s R>
c l a s s Add i t i on
const L& l ; const R& r ;
5 pub l i c :
Add i t i on ( const L& l , const R& r ) : l ( l ) , r ( r )
double g i v e ( i n t i ) const r e t u r n l . g i v e ( i ) + r . g i v e ( i ) ;
;
template <typename L , c l a s s R>
10 c l a s s S c a l a r M u l t i p l i c a t i o n
const L l ; const R& r ;
pub l i c :
S c a l a r M u l t i p l i c a t i o n ( const L& l , const R& r ) : l ( l ) , r ( r )
double g i v e ( i n t i ) const
15 r e t u r n l ∗ r . g i v e ( i ) ;
;
template <c l a s s L , c l a s s R>
i n l i n e Add i t i on <L ,R> ope ra to r+(const L& l , const R& r )
20 r e t u r n Add i t i on <L ,R>( l , r ) ;
template <c l a s s R>
i n l i n e S c a l a r M u l t i p l i c a t i o n <double ,R> ope ra to r ∗( double l , const R& r )
r e t u r n S c a l a r Mu l t i p l i c a t i o n <double , R>( l , r ) ;
25
c l a s s Vector
. . .
template <c l a s s E>
vo id ope ra to r=(const E& e )
30 f o r ( i n t i =0; i<s i z e ; ++i )
data [ i ] = e . g i v e ( i ) ;
;
// end o f namespace
Doch diese Implementierung mit allgemeinen Templates fuhrt zu einigen unbeabsichtigten Neben-
effekten oder auch Fehlern in der Anwendung. Der Zuweisungsoperator in der Klasse Vector bietet
Benutzern die Moglichkeit, einem Vektor jedes Objekt zuweisen zu konnen, bis zu der Stelle, an wel-
cher die Funktion give aufgerufen wird. Damit erhalt man bei einer Zuweisung wie
Vector a ;
. . .
a = 5 . ;
eine nur schwer verstandliche Fehlermeldung, dass fur ein Element eines Nicht-Klassentyps versucht
wurde die give-Funktion aufzurufen. Stattdessen sollte der Compiler zum besseren Verstandnis melden,
dass keine passende Zuweisung dieser Art existiert. Dieses Problem konnte dadurch gelost werden,
indem der Zuweisungsoperator fur alle Operationsklassen uberladen wird. Dies widersspricht aber der
ursprunglichen Idee eine kurzere und einfachere Programmierung zu erzielen.
Daneben kann es zu einigen Problemen bei der Auswahl bzw. Instantiierung der templatisierten
Operatoren kommen. Diese konnen mit bestehenden Versionen in Konflikt geraten und zu Mehrdeu-
tigkeiten fuhren, siehe auch Abschnitt 2.2.1. Betrachten wir dazu als Beispiel die Programmierung
von komplexen Vektoren mit der skalaren Multiplikation, wie in Listing 5.1. Mochte man hiermit eine
einfache Multiplikation mit einem double-Wert durchfuhren,
40
KAPITEL 5. EINFACHE ET-IMPLEMENTIERUNG
Vector <s t d : : complex<double> > a , b ;
. . .
b = 5 . ∗ a ;
treten intern vorher nicht zu erwartende Mehrdeutigkeiten fur die interne Multiplikation eines double-
Wertes mit einem std :: complex<double>-Objekt auf (Zeile 15 in Listing 5.1).
An dieser Stelle stehen dem Compiler zwei Multiplikationsoperatoren zur Verfugung. Zum einen der
in der complex-Klasse definierte Operator
template <typename T>
complex<T> ope ra to r ∗( const T& a , const complex<T> b ) ;
welchen der Compiler eigentlich instantiieren musste. Zum anderen findet der Compiler im Namespace
myExpr auch noch den Operator
template <c l a s s R>
i n l i n e S c a l a r Mu l t i p l i c a t i o n <double , R>
ope ra to r ∗( double l , const R& r ) ;
der nach entsprechender Instantiierung ebenfalls zu den Parametern passen wurde. Zwar konnte auch
dieses Problem durch die Einfuhrung von samtlichen Spezialisierungen der Operatoren fur die Ope-
rationsklassen gelost werden, doch war die anfangliche Intention einen kurzeren Programmcode zu
erhalten.
5.3 Einfache, typsichere ET
Aufbauend auf der CRTP-Technik (Abschnitt 2.3.6) und der automatischen Typumwandlung (Ab-
schnitt 2.2.2) wird im Folgenden eine einfache und typsichere Programmierung der ET-Technik prasen-
tiert. Fur diese Implementierung, die anders als die vorher gezeigte Variante nicht zu Mehrdeutigkeiten
beim Instantiieren fuhren kann, benotigt man jedoch eine Wrapper-Klasse. Zu dieser lasst sich nach den
Ergebnissen aus Kapitel 4 feststellen, dass sie lediglich als einheitliche Schnittstelle der moglichen Aus-
drucke dient. Weiter wird diese Wrapper-Klasse immer nur in Zusammenhang mit einer abgeleiteten
Klasse verwendet.
Deshalb bietet es sich an, die Wrapper-Klasse mittels der bereits in Kapitel 4 entwickelten Ideen
zu realisieren und die Operationsklassen, sowie die Vektordatenstruktur von dieser Wrapper-Klasse
abzuleiten.
Der Programmcode 5.2 prasentiert die resultierende einfache ET-Implementierung. Darin enthalt der
Wrapper Expr einen Cast-Operator, mittels dem der Compiler ein durch den Wrapper gekapseltes Objekt
in das ursprungliche Objekt zuruck konvertieren kann. Zusatzlich ist eine Auswertungsfunktion give
definiert, welche die Berechnungen der i-ten Komponente an das gekapselte Objekt weitergibt. Weiter
folgt die Implementierung der Operationsklasse fur die Addition zweier Ausdrucke. Dabei werden der
linke und rechte Teil des Ausdrucksbaumes als konstante Referenzen gespeichert. Die Auswertung der
i-ten Komponente berechnet die Summe der jeweiligen Ergebnisse der Teilbaume. Zusatzlich wird die
Operationsklasse fur die Multiplikation von beliebigen skalaren Werten mit Ausdrucken gezeigt. In
den Zeilen 29 bis 40 enthalt Listing 5.2 die uberladenen Operatoren + und ∗. Dazu ist zu bemerken,
dass die Template-Parameter der Operationsklassen ohne die kapselnden Wrapper-Typen angegeben
werden. Dadurch wird beim Ubergeben der Operanden an die Konstruktoren der Cast-Operator der
Wrapper-Klasse aufgerufen.
Die Vector-Klasse selbst wird ebenfalls mittels CRTP als Ableitung der Wrapper-Klasse definiert.
Damit ist nur eine Version je Operator zu implementieren, denn ein Vector-Objekt hat dadurch den Typ
Expr und passt somit zu den bereits uberladenen Operatoren. Die Schnittstelle im Zuweisungsoperator
41
5.3. EINFACHE, TYPSICHERE ET
bildet ebenfalls ein Objekt vom Typ Expr, und da die Wrapper-Klasse ebenfalls eine give-Funktion
besitzt, kann das ubergebene Objekt einfach ausgewertet werden.
Listing 5.2: Programm-Code einer einfachen ET-Variante.
template <c l a s s E>
s t r u c t Expr
ope ra to r const E& () const
r e t u r n ∗ s t a t i c c a s t <const E∗>( t h i s ) ;
5
double g i v e ( i n t i ) const
r e t u r n s t a t i c c a s t <const E&>(∗ t h i s ) . g i v e ( i ) ;
;
10
template <c l a s s L , c l a s s R>
c l a s s Add i t i on : pub l i c Expr<Add i t i on<L ,R> >
const L& l ;
const R& r ;
15 pub l i c :
Add i t i on ( const L& l , const R& r ) : l ( l ) , r ( r )
double g i v e ( i n t i ) const r e t u r n l . g i v e ( i ) + r . g i v e ( i ) ;
;
20 template <typename L , c l a s s R>
c l a s s S c a l a r M u l t i p l i c a t i o n
: pub l i c Expr<S c a l a r M u l t i p l i c a t i o n <L ,R> >
const L l ;
const R& r ;
25 pub l i c :
S c a l a r M u l t i p l i c a t i o n ( const L& l , const R& r ) : l ( l ) , r ( r )
double g i v e ( i n t i ) const r e t u r n l ∗ r . g i v e ( i ) ;
;
30 template <c l a s s L , c l a s s R>
i n l i n e Add i t i on <L ,R>
ope ra to r+(const Expr<L>& l , const Expr<R>& r )
r e t u r n Add i t i on <L ,R>( l , r ) ;
35
template <c l a s s R>
i n l i n e S c a l a r Mu l t i p l i c a t i o n <double , R>
ope ra to r ∗( double l , const Expr<R>& r )
r e t u r n S c a l a r Mu l t i p l i c a t i o n <double , R>( l , r ) ;
40
c l a s s Vector : pub l i c Expr<Vector >
. . .
template <c l a s s E>
45 vo id ope ra to r=(const Expr<E>& e )
f o r ( i n t i =0; i<s i z e ; ++i )
data [ i ] = e . g i v e ( i ) ;
;
Insgesamt bildet Listing 5.2 damit eine komplette Implementierung fur die komponentenweise Addi-
tion und Multiplikation von Vektoren. Es fehlen lediglich die Konstruktoren sowie der Destruktor der
Vektor Klasse. Wie bei der vorherigen Implementierung ohne Wrapper-Klasse konnen hier alle Ope-
randen als Referenzen gespeichert werden, da auch hier in den uberladenen Operatoren keine lokalen
Objekte erzeugt werden.
Als Variante dieser Implementierung kann auf die Auswertungsfunktion in der Wrapper-Klasse ver-
42
KAPITEL 5. EINFACHE ET-IMPLEMENTIERUNG
zichtet werden. Dafur muss aber im Zuweisungsoperator der Vektorklasse das ubergebene gekapselte
Objekt vom Typ Expr<E> erst explizit in E umgewandelt werden.
template <c l a s s E>
s t r u c t Expr
i n l i n e ope ra to r const E& () const
r e t u r n ∗ s t a t i c c a s t <const E∗>( t h i s ) ;
;
. . .
c l a s s Vector : pub l i c Expr<Vector >
. . .
template <c l a s s E>
vo id ope ra to r=(const Expr<E>& e )
const E& expr ( e ) ;
f o r ( i n t i =0; i<s i z e ; ++i )
data [ i ] = expr . g i v e ( i ) ;
;
5.4 Einfache ET fur temporare Ausdrucke
Eine ET-Programmierung die – wie vorher gezeigt – nur mit Referenzen in den Operationsklassen
arbeitet, kann jedoch nicht immer benutzt werden. Beispielsweise muss fur die Speicherung von ET-
Objekten eine Implementierung verwendet werden, die durchgangig voll abgespeicherte Operanden
in den Operationsklassen besitzt, wie in Abschnitt 7.3 beschrieben. Da in diesem Fall die gesamten
Ausdrucke mit den Terminalen gesichert werden sollen, kann einfach die vorherige Implementierung
ohne die Referenzen der Membervariablen verwendet werden.
Beispielsweise entstehen beim Aufbau eines Ausdrucksbaumes fur geschachtelte ET-Programme (Ka-
pitel 7.1) temporare Operanden, die wiederholt kopiert und als volle Objekte abgespeichert werden
mussen. Hierbei ist es nicht sinnvoll eine Implementierung mit durchweg vollen Objekten zu verwen-
den, denn somit wurden auch die Vektoren in jedem Operanden voll kopiert und abgespeichert. Dies
hatte vor allem bei großen Vektoren einen nachteiligen Einfluss auf die Performance. In den herkomm-
lichen ET-Implementierungen wurde dies generell durch eine Spezialisierung der Operationsklassen
bzw. durch entsprechend uberladene Operatoren realisiert [Han96, Vel01, Fur97]. Dies hatte naturlich
wiederum langwierige und wegen den zusatzlichen Spezialisierungen auch schwerer wartbare Imple-
mentierungen zur Folge.
Listing 5.3: ET-Implementierung zur Speicherung temporarer Operanden.
template<c l a s s E>
s t r u c t Expr
ope ra to r const E& () const r e t u r n ∗ s t a t i c c a s t <const E∗>( t h i s ) ;
4 ;
template<c l a s s E>
s t r u c t Expr<const E&>
ope ra to r const E& () const r e t u r n ∗ s t a t i c c a s t <const E∗>( t h i s ) ;
;
9 template <c l a s s L , c l a s s R>
c l a s s Add i t i on : pub l i c Expr<Add i t i on<L ,R> >
L l ; R r ;
pub l i c :
Add i t i on (L l , R r ) : l ( l ) , r ( r )
14 double g i v e ( i n t i ) const . . .
;
c l a s s Vector : pub l i c Expr<const Vector&> . . . ;
43
5.4. EINFACHE ET FUR TEMPORARE AUSDRUCKE
Aus diesem Grund wurde fur die vorliegende Arbeit eine zusatzliche Implementierungsvariante der
ET-Version aus Listing 5.2 entwickelt, die mit voll abgespeicherten Operanden arbeitet, außer, wenn
es sich bei den Operanden um Vector-Objekte handelt. Der resultierende Programmcode benotigt nur
eine zusatzliche Spezialisierung der Wrapper-Klasse.
Dazu wird die Klasse Vector von der Wrapper-Klasse nicht mit sich selbst als Typ abgeleitet, son-
dern mittels Expr<const Vector&>. Da der benutzerdefinierte Konvertierungsoperator der bestehenden
Wrapper-Implementierung mit diesem Template-Typ nicht instantiiert werden kann, muss zusatzlich
eine spezialisierte Version der Klasse Expr implementiert werden. Diese Spezialisierung behandelt den
Fall, dass der Template-Typ eine konstante Referenz ist (Zeilen 5 bis 8 in Listing 5.3). In der Operati-
onsklasse werden die Operanden nun als ganze Objekte verwendet, ebenso in der Parameterubergabe
des Konstruktors.
Wird diese Implementierung benutzt um die Summe von zwei Vektoren zu berechnen, etwa a = b + c,
so wird uber den uberladenen Plusoperator – unter Verwendung der Konvertierung aus Zeile 7 in Listing
5.3 – ein Objekt vom Typ
Add i t i on <const Vector &, const Vector&>
generiert, in dem nur Referenzen der Vektoren abgespeichert sind. Addiert man aber drei Vektoren,
so ergibt sich auf Grund der anderen Vererbung der Additionsklasse ein Objekt mit dem folgenden
Datentyp:
Add i t i on <Add i t i on <const Vector &, const Vector &>,const Vector &>.
Durch die spezialisierte Vererbung der Vektorklasse kann also die Art der Operandenabspeicherung
gesteuert werden.
Alle hier prasentierten einfachen Implementierungen liefern naturlich ebenso performante Program-
me wie die traditionellen Versionen von ET, da die Optimierungen mittels Inlining aquivalent durch-
gefuhrt werden konnen. Doch wie bereits angekundigt, bestehen bei den herkommlichen Implementie-
rungsvarianten – sowie auch bei der einfachen Version – weitere Optimierungslucken, die im folgenden
Kapitel genauer untersucht werden.
44
KAPITEL 6. FAST EXPRESSION TEMPLATES
6 Fast Expression Templates
6.1 ET im Hochleistungsrechnen
Da die Verwendung von C++ zur Programmierung von Losungen numerischer Problemstellungen
sehr verbreitet ist, werden die performanten ET-Bibliotheken insbesondere auch fur den Einsatz auf
Hochleistungsrechnern benotigt. Gerade auf diesen Rechnern ist es wichtig, dass die jeweiligen Compiler
die alle relevanten Optimierungen durchfuhren konnen, um die plattformspezifischen Rechenleistungen
entsprechend ausnutzen zu konnen.
6.1.1 Aliasing-Probleme bei ET
Wie bereits in Kapitel 4.1 angedeutet, konnen die geschachtelten Aufrufe zur Auswertung der ET-
Objekte durch Inlining optimiert werden, was im Prinzip einer direkten Auswertung der Arrays der
zugrunde liegenden Vektordatenstrukturen im C-Stil entspricht.
Doch eine gute Optimierung von vektorbasierten ET-Codes benotigt nicht nur ein vollstandiges
Inlining. Zusatzlich muss der Compiler die Aliase der Zeigervariablen auflosen, um ein performantes
Programm zu erhalten, siehe Kapitel 2.4.3. Auf Grund der geschachtelten Template-Strukturen und den
in Vektorklassen gekapselten Zeigern schaffen es Compiler aber nicht, diese Optimierung wie gewunscht
durchzufuhren, da sie nicht vollstandig sicherstellen konnen, wieviele Aliase fur den entsprechenden
Speicherbereich existieren. Zur genaueren Analyse wird das folgende Beispiel herangezogen, das wie die
Vektortriade auf Berechnungen mit der komponentenweisen Addition und Multiplikation von Vektoren
zuruckgreift.
Vector a , b , c ;
. . .
c = a + b ∗ a ;
Hierbei sollte nach Optimierung der give-Funktionen im Zuweisungsoperator (siehe dazu auch Ab-
schnitt 4.1) die Performance des folgenden Ausdrucks erreicht werden.
c . da ta [ i ] = a . da ta [ i ] + b . da ta [ i ]∗ a . da ta [ i ] ;
Doch innerhalb dieses Ausdrucks existieren mit den beiden Vektoren a im Ausdrucksbaum zwei Da-
tenzeiger, die auf den gleichen Speicherbereich zeigen. Bei solchen Aliasen in einem Ausdruck konnte
ein Zeiger zwischenzeitlich die Daten des zugrunde liegenden Arrays verandern und somit fur eine Ver-
wendung beim nachsten Zeiger ein neues Laden der Arraydaten in den Cache erforderlich machen. Um
bei Optimierungen keinen fehlerhaften Code zu erzeugen, haben C++-Compiler ein sehr konservatives
Konzept zum Auflosen solcher Mehrfachreferenzierungen. Konnen im Falle von wiederholt auftretenden
Variablen in einem Ausdruck diese Zeiger nicht miteinander identifiziert werden, beeintrachtigt dies
offensichtlich die Performance.
Diese Aliasing-Probleme von ET-Implementierungen wurden zuerst von Basetti, Davis und Quinlan
in [BDQ97] veroffentlicht. Darin zeigen sie die register spillage von ET-Codes, indem sie die Perfor-
mance von Ausdrucken bei einer steigenden Anzahl von Operationen betrachten. Daraus wird deut-
lich, dass die erreichbare Leistung der Ausdrucke mit wiederholt auftretenden Vektoren deutlich unter
der des C-Codes liegt. Diese register spillage lasst sich auf das vorher beschriebene Aliasing-Problem
45
6.2. FAST EXPRESSION TEMPLATES
zuruckfuhren. Zur Vermeidung dieser Probleme fuhrte die Bibliothek tvmet (tiny vector matrix expres-
sion templates) [Pet03] Implementierungen ein, die speziell fur kleine Vektoren ohne interne Zeiger
bzw. dynamische Arrays arbeiten. Stattdessen wurden die Vektorklassen um einen Template-Integer
erweitert, der die Dimension des Vektors enthalt, die dann naturlich nicht dynamisch ist. Intern wurden
damit die Daten nicht mit Zeigern und Arrays reprasentiert, sondern es wurden fur jede Dimension
eine Spezialisierung der Vektorklasse definiert, die eine entsprechende Zahl an Membervariablen be-
sitzt. Zur Verwendung dieser Vektoren muss also bereits mittels Templates die Dimension angegeben
werden, was naturlich die Flexibilitat der Anwendung einschrankt.
Neben dem nicht vollstandig aufgelosten Aliasing bestehen gerade fur kleine Vektoren noch große
Unterschiede in der Leistungsfahigkeit zwischen ET- und C-Codes. Dies liegt daran, dass der Aufbau
der ET-Objekte vor allem fur kleine Vektoren durch das Erstellen der Operationsklassen-Objekte die
Performance deutlich beeinflusst. Dies zeigen unter anderem die Laufzeitanalysen in Abschnitt 6.3. Im
Folgenden werden Implementierungsvarianten fur ET prasentiert, die den Compiler beim Auflosen des
Aliasings der Datenzeiger unterstutzen, sowie die Performance von kleinen Vektoren verbessern, ohne
dabei die Flexibilitat einzuschranken.
6.2 Fast Expression Templates
Die Programmierung von Fast Expression Templates (FET) wurde zunachst in einer sehr eingeschrank-
ten Form speziell fur die Hitachi SR8000 (Pseudo-Vektor-Maschine) entwickelt [LP03]. Diese Varian-
te kopierte die Datenpointer vor der Auswertung in globale Zeiger, was im Prinzip einem externen
Auflosen des Aliasings entspricht. Ein großer Nachteil war dabei jedoch, dass diese Implementierung
nur fur eine feststehende Maximalanzahl von Vektoren funktionierte, bzw. die Verwendung von mehr
Vektoren zusatzliche Erweiterungen im Code erforderte.
Die generelle Entwicklung der FET-Technik wurde in [HLP06b] und [HLP05] veroffentlicht. Dabei
wurden die Template-Nummerierung und die Meta-FET nur in Kombination betrachtet. Nachfolgend
werden die Methoden getrennt vorgestellt, denn bereits die Anwendung der Template-Nummerierung
allein lost die angesprochenen Aliasing-Probleme.
6.2.1 Template-Nummerierung der Vektorklassen
Im Rahmen der vorliegenden Arbeit wird ausgehend vom Listing 2.1 die dort aufgefuhrte Vektorklasse
um einen Integer als Template-Parameter erweitert. Dieser soll dazu dienen, Vektoren mit verschiedenen
Typen zu erzeugen und damit die Funktionalitat von Objekten einer Klasse (Vector) auf Objekte von
verschiedenen Klassentypen (Fast Vector<int> mit unterschiedlichen Integern) zu ubertragen. Zusatzlich
werden die Datenzeiger innerhalb der templatisierten Vektorklassen als statisch deklariert, woraus
folgt, dass alle Vektoren mit dem gleichen Template-Index den gleichen Datenzeiger besitzen. Der
Speicherplatz fur diesen Zeiger selbst muss – wie bei statischen Variablen ublich – außerhalb der
Klasse angefordert werden. Neben diesen Anderungen kann die Implementierung der Vektorklasse
ubernommen werden, was insbesondere heißt, dass das Array, auf welches der Pointer zeigt, weiterhin
dynamisch ist, und somit keinerlei Information uber Vektorgroßen zur Ubersetzungszeit benotigt wird.
Der ubrige Teil der ET-Implementierung, die Operationsklassen und die uberladenen Operatoren, bleibt
unverandert. Listing 6.1 zeigt dem entsprechend die Implementierung der Klasse Fast Vector.
Bei Verwendung dieser nummerierten Klasse besitzen alle Vektorobjekte mit identischem Index den
gleichen statischen Pointer und zeigen damit auf das gleiche Array. Deshalb muss bei der Verwendung
dieser Variante der Anwender jeden Vektor mit einem anderen Index initialisieren. Um Benutzungs-
fehler zu vermeiden, kann man mit einem einfachen Test innerhalb der Vektorklasse sicherstellen,
dass jeder nummerierte Vektor nur einmal auftritt. Dazu uberpruft man beispielsweise zu Beginn der
Konstruktoren, ob der Daten-Pointer noch auf NULL zeigt. Falls nicht, so ist der Fast Vector mit dem
46
KAPITEL 6. FAST EXPRESSION TEMPLATES
entsprechenden Template-Integer bereits in Benutzung.
Listing 6.1: Template-nummerierte Vektordatenstruktur.
template < i n t un ique I d >
c l a s s Fa s t Vec to r : Expr<Fas t Vecto r <un ique I d > >
p r i v a t e :
s t a t i c double ∗ data ;
i n t s i z e ;
pub l i c :
. . .
;
template < i n t un ique I d >
double ∗ Fas t Vecto r <un ique I d > : : da ta = NULL ;
Mittels dieser Template-Nummerierung und der Einfuhrung von statischen Pointern kann der Com-
piler beim Ubersetzen schon anhand der auftretenden Datentypen entscheiden, wie die statischen Zeiger
verwendet werden. Das Aliasing mehrfach auftretender gleicher Vektoren kann aufgelost werden, da
erkennbar ist, dass die statischen Pointer auf das selbe Datenarray zeigen und welche Zeiger dieses
Array wirklich verandern. Dadurch werden bei der Ausfuhrung des Programms bereits in den Cache
geladene Daten nicht wieder neu angefordert, was die vorher aufgefuhrten Probleme behebt.
Die Anwendung dieser Implementierung unterscheidet sich lediglich bei der Initialisierung der Vek-
toren von den vorher prasentierten Versionen. Der Anwender muss dabei die Fast Vector-Objekte mit
einem Template-Integer anlegen. Zusatzlich ist sicherzustellen, dass diese Integer eindeutig sind, was
– wie bereits erwahnt – durch eine entsprechende Implementierung unterstutzt werden kann. Daruber
hinaus sind keine weiteren Besonderheiten zu berucksichtigen, die Vektoren konnen in gewohnter Weise
verwendet werden.
Fa s t Vec to r <1> a ( s i z e ) ;
Fa s t Vec to r <2> b ( s i z e ) ;
Fa s t Vec to r <3> c ( s i z e ) ;
. . .
c = a + b ∗ a ;
Ein genauer Vergleich der Performance dieser Implementierung mit einer ohne ET wird in den
Abschnitten 6.3.2 und 6.3.3 behandelt. Zunachst soll aber eine ET-Implementierung prasentiert werden,
die ausschließlich mit Typinformationen arbeitet.
6.2.2 Meta-FET
Aufbauend auf der Template-Nummerierung der Vektoren kann eine Implementierung definiert werden,
welche lediglich die Typen der erzeugten Objekte benotigt. Die eindeutige Nummerierung der Vekto-
ren und den damit eingefuhrten statischen Zeigern, ordnet jedem Datenpointer einen eigenen Typ zu.
Dadurch kann durch die Benutzung von statischen Zugriffs- und Auswertungsfunktionen eine Auswer-
tung der ET rein uber die geschachtelten Template-Typen implementiert werden. Dadurch mussen die
Operanden nicht mehr als Member-Variablen in den Operationsklassen abgespeichert werden, so dass
auch keine spezifischen Konstruktoren mehr benotigt werden. Die Wrapper-Klasse besitzt auch keinen
Cast-Operator mehr, da auf Grund der rein typ-basierten ET keine Konvertierungen notig sind, siehe
dazu die Implementierung in Listing 6.2.
In den uberladenen Operatoren werden also nur leere Objekte erzeugt, da im Weiteren nur die Typen
fur die Auswertung verwendet werden. Diese Auswertung geschieht dann durch die statischen Aufrufe
der Operations- bzw. Vektorklassen.
47
6.2. FAST EXPRESSION TEMPLATES
Listing 6.2: Meta-FET-Implementierung ohne Abspeicherung der Operanden.
template <c l a s s A> s t r u c t Expr ;
template <c l a s s L , c l a s s R>
s t r u c t Add i t i on : pub l i c Expr<Add i t i on <L ,R> >
5 s t a t i c double g i v e ( i n t i )
r e t u r n L : : g i v e ( i )+R : : g i v e ( i ) ;
;
10 template <c l a s s L , c l a s s R>
i n l i n e Add i t i on <L ,R>
ope ra to r +(const Expr<L>& a , const Expr<R>& b)
r e t u r n Add i t i on <L ,R>() ;
15
template < i n t un ique I d >
c l a s s Fa s t Vec to r : Expr<Fas t Vecto r <un ique I d > >
. . .
s t a t i c double g i v e ( i n t i )
20 r e t u r n data [ i ] ;
template <c l a s s E>
vo id ope ra to r=(const Expr<E>& e )
f o r ( i n t i =0; i<s i z e ; ++i )
25 data [ i ] = E : : g i v e ( i ) ;
;
template < i n t un ique I d >
double ∗ Fas t Vecto r <un ique I d > : : da ta = NULL ;
Bei Erweiterungen der FET aus Listing 6.2 fur die Multiplikation von Vektoren mit Skalaren gibt
es jedoch zusatzliche Besonderheiten. Da diese Technik nur auf Typinformationen beruht, mussen aus-
drucksspezifische Daten in einem eigenen Typen gekapselt werden. Dazu sind genau wie bei Fast Vector
nummerierbare Klassen fur Skalare einzufuhren.
Listing 6.3: Nummerierbare Klasse zur FET-Kapselung von Skalaren.
template < i n t un ique I d >
c l a s s Double : pub l i c Expr<Double<un ique I d > >
double v a l u e ;
pub l i c :
Double ( double d ) : v a l u e ( d )
s t a t i c double g i v e ( i n t i )
r e t u r n v a l u e ;
;
template < i n t un ique I d >
double Double<un ique I d > : : v a l u e ;
Damit kann dann nach entsprechender Einfuhrung von Double-Variablen aus Listing 6.3 eine skalare
Multiplikation folgendermaßen durchgefuhrt werden.
Fas t Vecto r <1> a ; Fas t Vecto r <2> b ;
Double<1> c ( 5 . ) ;
. . .
a = c ∗ b ;
Wie wir spater noch sehen werden, ist diese zusatzliche Kapselung der skalaren Werte lediglich in sehr
eingeschrankten Teilen der FET-Anwendungen notig (Abschnitt 6.4).
48
KAPITEL 6. FAST EXPRESSION TEMPLATES
6.3 Leistungsanalyse der FET-Implementierungen
Nach der Einfuhrung der template-nummerierten Vektoren und der Meta-FET-Implementierung wird
nun die Performance dieser Varianten analysiert. Nach einigen allgemeinen Anmerkungen zu den
prasentierten Untersuchungen wird zunachst die Verwendung von FET auf Workstations erortert.
Danach werden die erzielten Performance-Resultate der Implementierung in Kombination mit auto-
matischen Vektorisierung auf Vektorrechnern bzw. der Parallelisierung mittels OpenMP auf Clustern
zusammengefasst und diskutiert.
6.3.1 Allgemeine Bemerkungen zur Analyse
Als erstes Testbeispiel zum Vergleich der verschiedenen Implementierungen wird die Performance der
Vektortriade betrachtet,
a = b + c ∗ d, (6.1)
die offensichtlich keine Aliase aufweist. Damit soll zunachst uberpruft werden, ob die neuen Implemen-
tierungen so effizient sind, wie die klassischen Varianten. Als zweites wird mit
b =
7∑
i=1
ai (6.2)
ein Beispiel mit massivem Aliasing diskutiert. Neben diesen etwas trivialen Beispielen werden zusatz-
lich die zwei- bzw. dreidimensionalen Poisson-Probleme auf einem strukturierten Gitter betrachtet.
Der Fokus liegt grundsatzlich in der Anwendbarkeit der FET-Implementierungen, und nicht auf beson-
ders komplex angelegten Problemstellungen. Diskretisiert werden die Probleme durch die Standard-
Sterne der Finiten Differenzen (Funf-Punkte-Stern in 2D) sowie der Finiten Elemente Diskretisierungen
(Neun-Punkte- bzw. 27-Punkte-Stern). Um eine durchgangige Auflosung des Aliasings untersuchen zu
konnen, wird ein Jacobi-Verfahren zur Losung der numerischen linearen Gleichungen benutzt [SB02].
Da beim Jacobi-Loser die Zuweisung des Ergebnisses nach jedem Iterationsschritt erfolgt – anders als
beim Gauß-Seidel-Verfahren – mussen keine Daten wahrend eines Berechnungsschrittes neu geladen
werden.
Betrachtet man die Performance der ET- und FET-Implementierungen mittels der durchgefuhrten
Rechenoperationen pro Zeiteinheit (FLOPs), so entsteht bei dem Ausdruck (6.2) das Problem, dass
die Effizienz der FET-Implementierung uber der Peak-Performance der jeweiligen Rechner liegen kann.
Nach eingehender Betrachtung wird jedoch ersichtlich, dass der erzeugte Programmcode anstatt der
27 Additionen bzw. Multiplikationen durch Anwenden des Horner-Schemas
b = a(1 + a(1 + a(1 + a(1 + a(1 + a(1 + a))))))
nur noch 12 Gleitpunktoperationen benotigt. Bei klassischen ET konnen diese Optimierung zur Re-
duzierung der Operationen jedoch nicht durchgefuhrt werden, so dass ein Vergleich der FLOPs nicht
sinnvoll ist. Aus diesem Grund wird die Zeit betrachtet, die der Rechner benotigt, um den Ergeb-
nisvektor zu berechnen. Dazu werden die entsprechenden Ausdrucke mehrmals ausgewertet, um eine
sinnvolle Zeitmessung zu erhalten. Daraus berechnen sich dann die Werte Zeit pro Iterationsschritt.
Da die betrachteten Zeitunterschiede der verschiedenen Implementierungen exponentiell mit der
Große der eingesetzten Vektoren zunehmen, werden die prasentierten Graphen fur beide Achsen in
logarithmischer Skalierung dargestellt.
6.3.2 Effizienz auf Workstations
Zunachst werden die neuen Implementierungsvarianten auf herkommlichen Workstations, einem Intel
Pentium 4 bzw. einem Intel Dual Core Rechner untersucht. Eine ausfuhrliche Beschreibung der platt-
formspezifischen Daten ist in Anhang D.1.1 bzw. D.1.2 aufgefuhrt. Als Compiler werden verschiedene
49
6.3. LEISTUNGSANALYSE DER FET-IMPLEMENTIERUNGEN
Versionen des GNU-C++-Compilers (Versionen 3.3.2 bis 4.1.0) sowie den Intel-C++-Compiler (Ver-
sion 8.1) verwendet. Da die erhaltenen Ergebnisse alle sehr ahnlich sind, werden hier die aktuellsten
Ergebnisse auf einem Intel Dual Core prasentiert, ubersetzt mit dem GNU-Compiler, Version 4.1.0.
Abbildung 6.1 enthalt zwei Graphen mit je vier Performance-Kurven, einer Implementierung in C
ohne ET (NET), einer Version mit klassischen ET (CET), die Variante mit nummerierten Vektoren
(EET) sowie eine Programmierung mittels Fast Expression Templates (FET).
'RÚEDER6EKTOREN
:E
ITP
RO)
TERA
TIO
N
.%4 # #ODEOHNE%4#%4 +LASSISCHE%4%%4 .UMMERIERTE%4&%4 3CHNELLE%4
:E
ITP
RO)T
ERA
TIO
N
'RÚEDER6EKTOREN
.%4 # #ODEOHNE%4#%4 +LASSISCHE%4%%4 .UMMERIERTE%4&%4 3CHNELLE%4
Abbildung 6.1: Vergleich der Performance von Implementierungen ohne ET (NET), klassischen ET
(CET), nummerierten Vektoren (EET) und FET auf einem Pentium 4, ubersetzt mit dem GNU-C++-
Compiler, Version 4.1.0. Links: Problem (6.2) mit massivem Aliasing. Rechts: 2D-Poisson (Funf-Punkte-
Stern) gelost mittels dem Jacobi-Verfahren.
Die linke Abbildung zeigt die Leistung der Implementierung des Ausdrucks (6.2), der unter massivem
Aliasing steht. Dabei erreicht die Meta-FET-Implementierung die Leistung der Programmierung in C,
die beidemale viermal, stellenweise sogar sechsmal schneller sind als die Variante mittels klassischen
ET. Die Implementierung mit nummerierten Vektoren ist ab einer Vektorgroße von 100 ebenso effizient
wie die Meta-FET-Version, lediglich fur kleinere Vektorgroßen zeigt sich der Overhead, der durch den
Aufbau der Ausdrucke entsteht.
Die rechte Grafik zeigt das Laufzeit-Verhalten der vier Implementierungen bei einem 2D-Poisson-
Problem, diskretisiert mittels einem Finite-Differenzen-Funf-Punkte-Stern. Da in diesem Beispiel das
Aliasing-Problem weniger ausgepragt ist, konnen hierbei auch nicht so große Unterschiede festgestellt
werden. Wie vorher lauft die Meta-FET-Variante so gut wie der C-Code. Die nummerierten Vektoren
ergeben fur Probleme, die in den Cache passen, 50% dieser Laufzeiten. Betrachtet man jedoch die
klassische ET-Variante, so hat die Verwendung von nummerierten Vektoren bereits eine Verdopplung
der Laufzeiten zur Folge, die Anwendung von Meta-FET sogar eine Vervierfachung.
6.3.3 FET auf Hochleistungsrechnern
Die angesprochenen Aliasing-Probleme sind zwar – wie in den Grafiken 6.1 gezeigt – auch auf gewohn-
lichen Workstations mit den gangigen Compilern zu bemerken. Doch die Auswirkungen auf die Perfor-
mance sind auf Hochleistungsrechnern, wie z.B. auf Plattformen, die mit automatischer Vektorisierung
oder Parallelisierung arbeiten, deutlicher ausgepragt. Denn fur die parallelen Aufteilungen und Berech-
nungen ist ein vollstandiges Auflosen der Abhangigkeiten unverzichtbar, um effiziente Programme zu
erhalten.
Die folgenden Performance-Ergebnisse beschranken sich auf Meta-FET-Programme, herkommliche
ET-Varianten und die entsprechenden Codes ohne die Verwendung von ET. Da die nummerierten
Vektoren mit einer klassischen ET-Programmierung ab einer Vektorgroße von 100 ebenso schnell wie die
50
KAPITEL 6. FAST EXPRESSION TEMPLATES
Meta-FET laufen, wird hier aus Grunden der Ubersichtlichkeit auf die Prasentation dieser Ergebnisse
verzichtet.
Performance auf Vektorcomputern
Fur eine effektive Vektorisierung ist ein vollstandiges Auflosen der Pointerabhangigkeiten unumgang-
lich [HLR05]. Um die Verbesserungen der FET-Variante bzgl. einer automatischen Vektorisierung zu
testen, werden die Implementierungen auf der Hitachi SR8000 am LRZ Munchen sowie der NEC
SX-6/48M am HLRS Stuttgart betrachtet. Eine genaue Beschreibung der Plattformen findet sich in
Anhang D.2.1 bzw. D.2.2. Die nachfolgenden Performance-Ergebnisse beschranken sich auf Untersu-
chungen der NEC SX-6/48M, da die resultierenden Leistungskurven auf der Hitachi sehr ahnlich sind
und keine wesentlichen Unterschiede aufzeigen.
,ËNGEDER6EKTOREN
NOIT
ARET)
ORPTI
E:
.%4 # #ODEOHNE%4# % 4 + L A S S I S C H E % 4&%4 &AST %4
,ËNGEDER6EKTOREN
NOIT
ARET)
ORPTI
E:
.%4 # #ODEOHNE%4# % 4 + L A S S I S C H E % 4&%4 &AST %4
Abbildung 6.2: Performance-Ergebnisse auf der NEC SX-6. Links: Betrachtung der Vektortriade (6.1),
bei der die Vorteile der FET-Implementierung fur kleine Vektoren erkennbar sind. Rechts: (6.2), ein
Term mit massivem Aliasing, woraus die Unterschiede der klassischen ET zu der FET-Variante deutlich
erkennbar sind.
Zur Analyse der Effizienz der Implementierungen wird zunachst die Vektortriade betrachtet, die
keinerlei Aliasing-Probleme aufwirft. Dennoch kann fur kleine Vektoren (bis zu einer Lange von 1000)
eine deutliche Verbesserung durch die FET-Implementierung festgestellt werden, was wiederum auf
den schnelleren Aufbau der ET-Objekte zuruckzufuhren ist. Die rechte Grafik in Abbildung 6.2 zeigt
die Performance-Kurven fur den großen Ausdruck, in dem 27 Mal der selbe Vektor auftritt. Hier ist
die Leistungssteigerung der FET gegenuber der herkommlichen Implementierung deutlich sichtbar. Die
FET sowie die C-Programmierung laufen etwa zehnmal schneller als die klassische ET-Variante.
Neben diesen positiven Ergebnissen vereinfachte sich zudem die Programmierung der Parallelis-
ierung. Dazu stehen bei Verwendung des NEC-C++-Compilers (sc++) entsprechende Pragmas zur
Verfugung, mittels derer angegeben wird, welche Schleifen parallelisiert werden sollen. Lediglich bei
einfachen Schleifen ist eine automatische Parallelisierung der Schleifen moglich. Wahrend bei der Im-
plementierung der herkommlichen ET massiv mit den Pragmas gearbeitet werden musste, um eine
Parallelisierung durch den Compiler zu erreichen, ist die Vektorisierung der FET-Implementierungen
ohne zusatzliche Verwendung von Pragmas moglich.
Zusatzlich werden mit der numerischen Losung der 2D- bzw. 3D-Poisson-Gleichung auch anwen-
dungsbezogenere Problemstellungen betrachtet. Zur Diskretisierung auf Gittern bestehend aus Qua-
draten bzw. Wurfeln wird die Finite Elemente Methode verwendet, die zu Neun- bzw. 27-Punkt-
Sternen fuhrte. Die resultierenden Gleichungssysteme werden wiederum mit dem leicht parallelisierba-
ren Jacobi-Verfahren gelost. Abbildung 6.3 zeigt die zugehorigen Performance-Kurven, woraus wieder
deutlich die Leistungssteigerung der FET-Implementierung zu erkennen ist. Die FET erreichen da-
51
6.3. LEISTUNGSANALYSE DER FET-IMPLEMENTIERUNGEN
bei wiederum die Performance der C-Codes, was deutlich zeigt, dass die Optimierungen die Aliase
vollstandig auflosen konnen.
,ËNGEDER6EKTOREN
NOIT
ARET)
ORPTI
E:
.%4 # #ODEOHNE%4#%4 +LASSISCHE%4&%4 &AST%4
NOIT
ARET)
ORPTI
E:
.%4 # #ODEOHNE%4#%4 +LASSISCHE%4&%4 &AST%4
,ËNGEDER6EKTOREN
Abbildung 6.3: Leistungs-Kurven auf der NEC SX-6. Numerische Losung der 2D- bzw. 3D-Poisson-
Gleichung durch Diskretisierung mittels der Finiten Elemente Methode und Anwendung eines leicht
parallelisierbaren Jacobi-Verfahrens.
Durch die FET-Implementierung konnen die Compiler also auch auf Hochleistungsrechnern die er-
wartete Performance erreichen. Dadurch konnen also FET auch fur die Problemlosung auf diesen Platt-
formen eingesetzt werden, ohne einen Leistungsverlust erwarten zu mussen. Daruber hinaus mussen
die nummerierten Variablen nicht uber den gesamten Programmcode verwendet werden, siehe dazu
Abschnitt 6.4.
Parallelisierung mittels OpenMP
Neben den Ergebnissen der Implementierungsvarianten mit automatischer Vektorisierung, werden eben-
falls die Effizienz der FET bei der Verwendung von OpenMP betrachtet. Dabei wird diese automatische
Parallelisierung auf einem AMD Opteron Cluster am Lehrstuhl Informatik 10 in Erlangen untersucht.
Dieser Rechner besteht aus neun zweifach sowie acht vierfach Knoten, jeweils mit gemeinsamen Spei-
cher, genauer beschrieben in Anhang D.2.3. Die Implementierungen werden mittels dem Intel C++-
Compiler, Version 8.1 sowie dem OpenMP-Flag zur automatischen Parallelisierung ubersetzt, wobei in
den Programmcodes die entsprechenden Pragmas fur die Anwendung von OpenMP in den Iterations-
schleifen eingefugt werden [CDK+01, EV01].
Wie bereits auf den Vektorrechnern erreichen die FET-Implementierungen bei einer Parallelisierung
mittels OpenMP die Performance der C-Programme. Diese liegt wiederum deutlich uber der erreichten
Leistung der herkommlichen ET-Versionen, wobei der Unterschied wegen der geringen Parallelisierung
weniger deutlich ausfallt als auf den Vektorplattformen.
Abbildung 6.4 prasentiert zwei Performance-Kurven, die sich auf die Ergebnisse auf einem vierfach
Knoten des AMD Opteron Clusters beziehen. Die linke Grafik zeigt dabei die Laufzeit pro Auswer-
tungsschritt des großen Ausdrucks (6.2). Dabei erreicht die FET-Implementierung eine sieben- bis
acht-mal bessere Performance. Die rechte Grafik enthalt die Laufzeitergebnisse zur Losung des zwei-
dimensionalen Poisson-Problems. Auch hier kann – trotz eines nur geringen Aliasings – eine deutliche
Verbesserung mittels FET erzielt werden.
Nach diesen Untersuchungen der Leistungsfahigkeit von FET-Implementierungen sollen nun die Pro-
grammteile eingeschrankt werden, in denen die FET benutzt werden mussen, um weiterhin einen per-
formanten Code zu erhalten.
52
KAPITEL 6. FAST EXPRESSION TEMPLATES
NOIT
ARET)
ORPTI
E:
,ËNGEDER6EKTOREN
.%4 # #ODEOHNE%4#%4 +LASSISCHE%4&%4 &AST%4
.%4 # #ODEOHNE%4#%4 +LASSISCHE%4&%4 &AST%4
NOIT
ARET)
ORPTI
E:
,ËNGEDER6EKTOREN
Abbildung 6.4: Laufzeit auf einem AMD Opteron Cluster unter Verwendung eines vierfach Knotens
mit OpenMP-Parallelisierung. Die linke Grafk stellt die Ergebnisse des Ausdrucks b =P
7
i=1a
i dar,
woraus ersichtlich wird, dass die FET-Implementierung die Performance des C-Codes erreicht. Ana-
loge Betrachtungen ergeben sich aus der rechten Kurve, worin die Laufzeiten pro Iteration fur das
zweidimensionale Poisson-Problem dargestellt sind.
6.4 Praktische Anwendung von FET
Durch das Durchnummerieren der Vektoren mittels Templates und die Kapselung aller Skalare in
gesonderte nummerierte Klassen verkompliziert sich die Verwendung von FET-Bibliotheken. Neben
der Indizierung durch den Benutzer muss dieser, um eine korrekte Implementierung zu erhalten, auch
noch darauf achten, dass die kontextbezogene Nummerierung eindeutig ist.
6.4.1 Einschrankung auf rechenintensive Programmteile
Der jeweilige Wirkungskontext der nummerierten Objekte muss sich nicht uber das gesamte Anwen-
dungsprogramm erstrecken. Die FET-Implementierungen liefern neben dem schnellen Aufbau der ET-
Objekte vor allem durch das Auflosen der Aliase einen performanteren Programmcode. Gerade diese
Aliasing-Probleme treten jedoch nur in Ausdrucken auf, in denen mehrfach die gleichen Variablen
vorkommen. Dabei handelt es sich in den meisten Fallen um die zentralen Berechnungseinheiten im
Programm, also um die Stellen, an denen sich die performance-kritischen Ausdrucke befinden. Aus
diesem Grund ist es fur den Erhalt von effizienten Programmen vollig ausreichend, die Verwendung
der FET auf die rechenintensiven Stellen im Anwendungscode zu beschranken.
Listing 6.4: Template-nummerierte Vektordatenstruktur zur eingeschrankten Verwendung in rechen-
intensiven Programmteilen.
template < i n t un ique I d >
c l a s s Fa s t Vec to r : Expr<Fas t Vecto r <un ique I d > >
. . .
F a s t Vec to r ( const Vector& vec ) : da ta ( vec . d a t a p o i n t e r ( ) ) ;
vo id r e s e t ( ) data = NULL ;
;
template < i n t un ique I d >
double∗ Fas t Vecto r <un ique I d > : : da ta = NULL ;
Basierend auf einer Implementierung, welche die herkommliche ET-Version, eine Vektorklasse im
ursprunglichen Stil von Listing 2.1 sowie eine template-nummerierte Vektorklasse umfasst, konnen
Anwendungen wie bisher programmiert werden und zusatzlich die aufwandigen Berechnungen mit
schnellen Vektoren behandelt werden. Dazu initialisiert man vor Beginn eines performance-kritischen
53
6.4. PRAKTISCHE ANWENDUNG VON FET
Programmteils die nummerierbaren Variablen mittels der zugehorigen Vektordaten. Im Falle von ar-
raybasierten Datenstrukturen ist dabei keine volle Kopie der Objekte notig, lediglich die Datenzeiger
sind an den Fast Vector zuzuweisen, siehe Listing 6.4.
Nach der Initialisierung der Fast Vector-Objekte konnen die Berechnungen mit den FET durchgefuhrt
werden. Nach Ende des rechenintensiven Programmteils ist keine Ruckzuweisung der Datenzeiger notig,
da sich die Lage der Zielarrays im Speicher nicht geandert hat, lediglich der Inhalt. In folgenden
performance-kritischen Teilen konnen die bereits verwendeten FET-Vektoren wieder fur weitere Be-
rechnungen benutzt werden, da die Daten im ursprunglichen Vektor noch erhalten sind. Um jedoch
zu vermeiden mit falsch zugewiesenen Vektoren zu arbeiten, sollten nach einer solchen FET-Einheit
die statischen Datenzeiger der Fast Vector Objekte wieder auf NULL gesetzt werden ( reset -Funktion). Ein
exemplarischer Programmaufbau zu dieser eingeschrankten Anwendung findet sich in Listing 6.5.
Generell ist es ausreichend zu einer herkommlichen ET-Implementierung eine nummerierte Daten-
struktur mit den entsprechenden Schnittstellen (wie in Listing 6.4) hinzuzufugen. Sollen aber speziell
Probleme mit kleinen Datenstrukturen, z.B. Polynomen oder raumlichen Vektoren kleiner Dimension,
behandelt werden (wie vorher in Abschnitt 6.3.2 gezeigt), so hat der Aufbau der Ausdrucksbaume
durchaus Einfluss auf die Performance. In solchen Fallen ist es dann sinnvoll die vollstandige statische
FET-Implementierung neben den herkommlichen ET zu verwenden, um eine schnellere Konstruktion
der Ausdrucke zu ermoglichen. Die Beschrankung auf die rechenintensiven Programmteile kann da-
bei analog wie vorher angewandt werden. Doch mussen dann neben den schnellen Vektoren auch alle
konstanten Werte bzw. Variablen mittels entsprechender Datenstrukturen (siehe Listing 6.3) gekapselt
und nummeriert werden.
Listing 6.5: Praktische Anwendung der FET-Implementierungen an rechenintensiven Stellen.
Vector a , b , c ;
. . .
// I n i a l i s i e r u n g , e i n f a c h e Rechnungen
. . .
5 // Anfang r e c h e n i n t e n s i v e r Programmtei l
Fas t Vecto r <1> a f a s t ( a ) ;
Fa s t Vecto r <2> b f a s t ( b ) ;
Fa s t Vecto r <3> c f a s t ( c ) ;
. . .
10 // s c h n e l l e Berechnungen
. . .
a f a s t . r e s e t ( ) ;
b f a s t . r e s e t ( ) ;
c f a s t . r e s e t ( ) ;
15 // Ende r e c h e n i n t e n s i v e r Programmtei l
. . .
// we i t e r e Benutzung von a , b , c wie gewohnt , z .B .
. . .
a . p r i n t ( )
Somit kann also die Verwendung der FET-Implementierungen, die vom Benutzer selbst zu num-
merieren sind, auf die relevanten Teile der Programme eingeschrankt werden. Der restliche Teil der
Anwendungen kann mittels der herkommlichen ET wie gewohnt genutzt werden.
6.4.2 Automatisches Nummerieren
Die einzigen Veranderungen fur die Benutzer von FET-Implementierungen ergeben sich durch das
Nummerieren der Variablen, wahrend die Anwendungen der Funktionen und uberladenen Operatoren
genauso wie bei den herkommlichen ET die gewunschten mathematischen Schnittstellen liefern. Zur
einfacheren Verwendung der nummerierten Variablen ware es naturlich hilfreich, wenn die Vergabe der
Nummerierung automatisch erfolgen wurde.
54
KAPITEL 6. FAST EXPRESSION TEMPLATES
Versucht man eine solche automatische Nummerierung innerhalb von C++ mittels Templates zu
losen, stoßt man schnell an die Grenzen der Technik. Die Template-Integer mussen zur Ubersetzungszeit
feststehen und fur jede Fast Vector-Variable wird ein neuer Index benotigt. Leider konnen die Template-
Integer nicht einfach wie normale Variablen erhoht werden, denn einmal gesetzt sind sie fix. Wurde
man fur den Template-Integer nun einen konstanten statischen Integer oder einen Enumerationswert
setzen, so wurde immer der gleiche Wert fur den Template eingesetzt, da die konstanten Werte ja
nicht verandert werden konnen. Auch Traits oder die Einfuhrung von Template-Klassen anstatt Integer
konnen nicht dazu beitragen unterschiedliche Typen fur die Fast Vector-Variablen zu erzeugen. Innerhalb
eines Ausdrucks konnen neue Typen (mittels Templates, Traits, etc.) aus bestehenden Typen erzeugt
werden, die dann aber nur innerhalb des Ausdrucks oder in neu generierten Typen zur Verfugung
stehen. Die konstanten Daten von bestehenden Typen konnen nicht verandert werden, weshalb die
generierte Typinformation nur aus den neu erzeugten Typen erhalten werden kann.
Da die Nummerierung nicht innerhalb der Sprache C++ losbar ist, kann man auf die Moglichkei-
ten des Pracompilers zuruckgreifen. Hier gibt es konstante Werte, die fur verschiedene Aufrufe auch
verschiedene Werte annehmen, z.B. das Makro LINE . Fur dieses Makro setzt der Precompiler immer
die aktuelle Zeilennummer ein, und erhalt somit fur verschiedene Zeilen unterschiedliche Integerwerte.
Wenn die Fast Vector-Variablen in verschiedenen Zeilen deklariert werden, kann man mittels
Fas t Vecto r < LINE > a ;
Fas t Vecto r < LINE > b ;
die gewunschte automatische Nummerierung erhalten. Doch sind die Deklarationen der nummerierten
Variablen uber mehrere Dateien verteilt, konnen sich dennoch wiederholende Indizes ergeben. Fur diese
Problemstellung passend bietet leider nur der Visual-C++-Pracompiler ein Makro ( COUNTER ), das
bei jedem Auftreten um eins erhoht wird.
Fur andere Compiler bleibt nur die Anwendung eines externen Programms oder Skripts, welches vor
der Ubersetzung die Dateien nach den Zahlmakros durchsucht und diese durch inkrementelle Integer
ersetzt. Doch dieses Konzept der automatischen Nummerierung lost lediglich die Generierung der
Template-Integer der Fast−Vector-Variablen. Die zusatzliche Kapselung der konstanten und variablen
Werte in den FET-Anwendungen konnen dadurch nicht ersetzt werden und mussen weiterhin zusatzlich
implementiert werden.
55
6.4. PRAKTISCHE ANWENDUNG VON FET
56
KAPITEL 7. ERWEITERTE METHODEN
7 Erweiterte Methoden fur ET
Nach Einfuhrung der verschiedenen Implementierungsvarianten werden in diesem Kapitel einige wei-
terfuhrende Methoden diskutiert, mittels derer die Funktionalitat von ET-Programmen verbessert
werden kann. Sofern nicht explizit angegeben, konnen die jeweiligen Techniken sowohl fur ET- als auch
FET-Implementierungen angewandt werden.
7.1 Typ-Minimierung durch Traits
Beim Entwickeln einer flexiblen Bibliothek mochte man die ET-Implementierung nicht auf einen be-
stimmten Datentyp festlegen, sondern den Grunddatentyp vom jeweiligen Anwendercode abhangig
machen. Dazu konnen in der ET-Implementierung einfach zusatzliche Template-Parameter eingefuhrt
werden, welche den verwendeten Datentyp aus der Vektorklasse bestimmen und damit an den notigen
Stellen ersetzen.
In einer so verallgemeinerten Programmierung konnen aber durchaus Grunddatenstrukturen mit
unterschiedlichen Datentypen zum Einsatz kommen, z.B. reelle und komplexe Vektoren. Um in diesem
Fall nicht alle reellen als komplexe Vektoren verwenden zu mussen, was sicherlich zu einer merkbaren
Leistungsverschlechterung fuhren wurde, kann man mittels der gezielten Anwendung von Traits eine
generalisierte Version der ET implementieren. Die notigen Typen innerhalb des ET-Codes werden
damit wieder automatisch durch den Compiler generiert und instantiiert. Als einfaches Beispiel dient
die Betrachtung der Kombinationen der Datentypen double und complex<double>. Tabelle 7.1 beschreibt
die moglichen Ergebnisse von binaren Operationen beim Aufeinandertreffen dieser beiden Typen.
Listing 7.1: Traits fur die Bestimmung des Ruckgabetyps.
template <typename L , typename R>
s t r u c t Resul tType
t ypede f s t d : : complex<double> Type ;
;
template <>
s t r u c t Resul tType<double , double>
t ypede f double Type ;
;
Dadurch kann also eine feste Struktur im Auswerten der Typen angegeben werden, die mittels dem
Traits-Mechanismus implementiert [HCKS98, LvG99, DA03] und somit wahrend der Ubersetzungszeit
Tabelle 7.1: Mogliche Typkombinationen von binaren Ausdrucken bei der Verwendung von double
und complex<double> als Grunddatentypen. Nur bei der Kombination von zwei double Werten resultiert
wiederum ein double. In den anderen drei Fallen erhalt man einen complex<double> als Ruckgabetyp.
Typ von L Typ von R Resultierender Typ
double double double
double complex<double> complex<double>
complex<double> double complex<double>
complex<double> complex<double> complex<double>
57
7.1. TYP-MINIMIERUNG DURCH TRAITS
ausgewertet werden kann. Dazu wird die Klasse ResultType in Listing 7.1 eingefuhrt, die fur allgemeine
Kombinationen den Ruckgabetyp als complex<double> definiert. Nur im Spezialfall, bei dem zwei double-
Werte aufeinander treffen, wird der Ergebnistyp auf double gesetzt.
Zur Anwendung dieser Traits in den ET-Implementierungen wird in jeder Operationsklasse sowie der
Vektorklasse ein Typ R Type eingefuhrt, der den Ergebnistyp des jeweiligen Objektes definiert. Listing
7.2 zeigt die Typdefinition, mittels derer uber einen typedef dieser Ergebnistyp bestimmt wird. Hierbei
ist besonders auf die Verwendung des Schlusselwortes typename zu achten, welches dem Compiler beim
Ubersetzen mitteilt, dass es sich bei den geschachtelten Argumenten um Datentypen handelt.
Listing 7.2: ET-Implementierung fur ubersetzergenerierte Ruckgabetypen.
template <c l a s s L , c l a s s R>
c l a s s Add i t i on : pub l i c Expr< Add i t i on<L ,R> >
. . .
t ypede f typename Resul tType<typename L : : R Type ,
typename R : : R Type > : : Type R Type ;
R Type g i v e ( i n t i ) const r e t u r n l . g i v e ( i ) + r . g i v e ( i ) ;
;
template <typename baseType>
c l a s s Vector : pub l i c Expr< Vector <baseType> >
baseType ∗ data ;
. . .
t ypede f baseType R Type ;
R Type g i v e ( i n t i ) const r e t u r n data [ i ] ;
;
Damit enthalt Listing 7.2 eine ET-Implementierung, die keinen festgelegten Grunddatentyp hat, und
abhangig von den Operanden der jeweils kleinste Ruckgabetyp generiert wird. Eine solche Typminimie-
rung mittels Traits ist naturlich analog fur mehr als zwei Datentypen erweiterbar. Dafur sind lediglich
zusatzliche Spezialisierungen der Klasse ResultType notig, aus denen der Compiler die Ruckgabetypen
wahrend des Ubersetzens bestimmen kann.
Solche allgemeinen ET-Implementierungen eroffnen vielfaltige Anwendungsmoglichkeiten, weil damit
nicht nur fundamentale Typen als Grunddatenstruktur verwendet werden konnen. Prinzipiell sind viel
komplexere Datentypen einsetzbar, wie z.B. Vektoren oder Matrizen. D.h. bei einer entsprechenden
Spezialisierung der ResultType-Klasse generiert der Compiler die fur die ET-Implementierung benotigten
Instantiierungen, etwa auch fur Vector<Vector<double> >. Dazu sind neben den Spezialisierungen keine
Anderungen der ET-Implementierung notig.
Da die Komponenten von solchen geschachtelten Datenstrukturen selbst arraybasierte Typen sind,
ist es jedoch sinnvoll dafur eigene ET-Implementierungen einzufuhren, welche Operationen nicht so-
fort, sondern erst bei der endgultigen Zuweisung berechnen. Diese verzogerte Auswertung der Kom-
ponenten kann wiederum durch die Definition entsprechender Ergebnistypen mit der Ruckgabe pas-
sender Operationsobjekte realisiert werden. Da diese Operationsobjekte aber in den Auswertungsme-
thoden temporar erstellt werden (Zeilen 11 und 12 in Listing 7.3), mussen diese als volle Objekte
mittels der ET-Implementierung aus Abschnitt 5.4 abgespeichert werden. Fur eine Datenstruktur wie
z.B. Vector<Vector<double> > wurde der folgende Programmcode (Listing 7.3) die Ruckgabetypen zur
verzogerten Auswertung der Komponenten definieren.
Dabei bezeichnet Vector Expr eine zusatzliche Wrapper-Klasse fur die Ausdrucke mit Ergebnistyp
Vector<Vector<double> > und Delayed Component Addition die entsprechende Operationsklasse fur die ver-
zogerte Addition der Komponenten, die in diesem Falle vom Typ Vector<double> sind. Zur Bestimmung
der Typen der Operanden in den Zeilen 13 bis 15 werden die Traits-Implementierungen von BASE
verwendet, da ein Zugriff auf Membertypen nur uber den Klassentyp moglich ist, nicht uber einen
Referenztyp.
58
KAPITEL 7. ERWEITERTE METHODEN
Listing 7.3: ET fur verzogerte Auswertungen der Komponentenoperationen.
template <c l a s s TYPE >
s t r u c t BASE t ypede f TYPE TYPE ; ;
template <c l a s s TYPE >
s t r u c t BASE<const TYPE &> t ypede f TYPE TYPE ; ;
5
template <c l a s s L , c l a s s R>
c l a s s Delayed Component Addi t ion
: pub l i c Vector Expr < Delayed Component Addi t ion <L ,R> >
L l ; R r ;
10 . . .
t ypede f Add i t i on<typename BASE<L> : :TYPE : : R Type ,
typename BASE<R> : :TYPE : : R Type> R Type ;
R Type g i v e ( i n t i ) const
r e t u r n l . g i v e ( i ) + r . g i v e ( i ) ; // Vektoren
15
;
Insgesamt werden also zwei verschiedene ET-Implementierungen benotigt, eine fur die Komponenten
selbst und eine weitere fur die geschachtelte Datenstruktur. Damit kann je nach Typ entschieden
werden, welche Operationsklasse benutzt werden soll. Es gibt jedoch auch Moglichkeiten verschiedene
Datenstrukturen mit nur einer ET-Programmierung zu realisieren.
7.2 ET fur verschiedene Datenstrukturen
Beim Entwickeln einer vielseitigen ET-Bibliothek, in der das effiziente Operatoruberladen fur verschie-
dene Datenstrukturen angewandt werden soll, muss nicht zwingendermaßen fur jede Datenstruktur
ein separate ET-Implementierung eingefuhrt werden. Zum Beispiel besitzen die meisten mathemati-
schen Objekte die Grundrechenarten +,−,∗, und die entsprechenden Operationsklassen konnen auch
fur verschiedene Datenstrukturen verwendet werden. Dazu sind prinzipiell nur die jeweiligen Auswer-
tungsfunktionen hinzuzufugen.
Dabei kann es jedoch passieren, dass unabsichtlicherweise Objekte addiert werden, die eigentlich nicht
kombiniert werden konnen, z.B. eine Addition von Vektoren mit Matrizen. Dadurch resultieren beim
Ubersetzen verschachtelte Fehlermeldungen mit der Folge der fehlgeschlagenen Auswertungsfunktionen.
Um diese schwer verstandlichen Compilerfehlermeldungen zu vermeiden, wird – aufbauend auf Listing
5.2 – eine erweiterte ET-Implementierung vorgestellt, die Compilezeit-Fehlermeldungen verhindert und
falsche Kombinationen zur Laufzeit aufdeckt [Fur97].
Hierbei werden die Wrapper-Klasse und die Operationsklassen um je einen zusatzlichen Template-
Parameter erweitert, der den Ergebnisdatentyp enthalt, also beispielsweise Vector. Desweiteren exis-
tieren zwei Versionen des Plus-Operators, wobei nur bei gleicher Grunddatenstruktur der Operanden
ein Addition-Objekt erzeugt wird. Unterscheiden sich die Grunddatentypen jedoch, so wird nur eine
Fehlermeldung ausgegeben. Da fur den Compiler aber auch in diesem Fall ein semantisch korrektes
Programm entstehen soll, wird mittels der Failure -Klasse ein Objekt erzeugt, das die notigen Dummy-
Auswertungsfunktionen definiert. Diese werden aber wegen dem Programmabbruch im Operator nicht
aufgerufen.
Prinzipiell kann eine solche ET-Implementierung also so erweitert werden, dass sie auch fur verschie-
dene Datenstrukturen sicher benutzt werden kann. Im Grunde wird der Compiler durch den zusatzli-
chen Template-Parameter im Programmcode 7.4 dazu veranlasst, die unterschiedlichen ET-Versionen
selbst zu generieren.
59
7.3. SPEICHERUNG VON ET
Listing 7.4: Programmiervariante zum Behandeln mehrerer Datenstrukturen und Laufzeitfehlermel-
dungen.
template <c l a s s E , c l a s s DataType>
s t r u c t Expr . . . ;
template <c l a s s L , c l a s s R, c l a s s DataType>
c l a s s Add i t i on : pub l i c Expr<Add i t i on <L ,R , DataType>,DataType>
5 . . . ;
template <c l a s s L , c l a s s R, c l a s s DT >
i n l i n e Add i t i on <L ,R,DT >
ope ra to r + ( const Expr<L ,DT >& l , const Expr<R,DT >& r )
r e t u r n Add i t i on <L ,R ,DT >( l , r )
10
s t r u c t F a i l u r e : pub l i c Expr<Fa i l u r e >
double g i v e ( i n t i ) const r e t u r n 0 ;
. . . // we i t e r e Dummy−Auswer tungs f unk t i onen
;
15 template <c l a s s L , c l a s s DTL, c l a s s R, c l a s s DTR >
F a i l u r e ope ra to r + ( const Expr<L ,DTL >& l , const Expr<R,DTR >& r )
s t d : : c e r r << ” Add i t i on o f i n compa t i b l e data t y pe s ! ” << s t d : : e nd l ;
e x i t (−1); r e t u r n F a i l u r e ( ) ;
7.3 Speicherung von ET
Die bisher prasentierten ET-Implementierungen sind darauf ausgelegt, die damit erstellten Ausdrucks-
baume innerhalb des semantischen Ausdrucks im Zuweisungsoperator auszuwerten. Doch bei der Pro-
grammierung einer benutzerfreundlichen und auch flexiblen ET-Bibliothek kann es durchaus wichtig
sein, dass ganze Ausdrucke spater im Programm weiter zur Verfugung stehen. Im Folgenden werden
deshalb zwei Implementierungsmechanismen zum Abspeichern von ET diskutiert, zum einen durch
Typkapselung, zum anderen mittels abstrakter Klassen.
7.3.1 Typkapselung und Formeln-Schablonen
Da die erzeugten ET-Objekte nach einer Zuweisung in eine neu angelegte Member-Variable geloscht
werden, mussen diese zur Speicherung kopiert werden, siehe auch Abschnitt 4.3. Deshalb ist eine
ET-Implementierung zu verwenden, die keine Referenzen in den Operationsklassen besitzt, sondern
volle Objekte (Listing 5.3). Damit werden bei einer Zuweisung alle Operationsobjekte kopiert, nicht
jedoch die Terminale des Ausdrucksbaumes, die explizit mittels Referenzen vom Wrapper abgeleitet
sind. Ein damit gespeicherter Ausdruck kann wie eine Formeldefinition verwendet werden, weil die darin
vorkommenden Variablen im weiteren Progammlauf veranderbar sind. Sollen jedoch auch die Variablen
selbst in die Formel kopiert werden, so ist in Listing 5.3 die Vektorklasse nicht als Referenztyp vom
Wrapper abzuleiten, sondern mittels Vector.
Der Typ eines ET-Ausdrucks wird wahrend der Ubersetzungszeit bestimmt. Daher kann einfach eine
Variable des entsprechenden Typs definiert werden.
Add i t i on <Vector , M u l t i p l i c a t i o n <Vector , Vector > > my expr = b + c ∗ d ;
Da diese Vorgehensweise sehr aufwandig und fehleranfallig ist, lasst man den Typen des Ausdrucks
uber das Makro typeof erzeugen. Die Verwendung von typeof ist zwar im C++-Standard definiert
und festgelegt, doch gibt es einige Compiler, welche die Funktionsweise dieses Schlusselwortes nicht im
vollen Umfang unterstutzen, wie etwa altere Versionen des Microsoft Visual-C++-Compilers.
Damit bei der Zuweisung eines ET-Objekts der zu speichernde Ausdruck nicht zweimal geschrieben
werden muss – bei Typ und Initialisierung – definiert man ein Makro, welches die entsprechende
Definition vornimmt.
60
KAPITEL 7. ERWEITERTE METHODEN
#de f i n e ET STORE(name , expr ) t y p e o f ( expr ) name ( expr )
ET STORE( my expr , b + c ∗ d ) ;
Soll ein ET-Ausdruck innerhalb einer neu anzulegenden Klasse gespeichert werden, so kann der Typ
der Membervariablen auf die gleiche Weise erzeugt werden. Bei der Initialisierung einer Speicherklasse
benotigt man den exakten Template-Typ fur die Membervariable. Um diesen nicht explizit angeben zu
mussen, kann man diesen wieder uber das typeof bestimmen lassen, oder man implementiert eine
entsprechende Creator-Funktion.
Listing 7.5: Speicherung von ET als Member-Variablen mit vollem Datentyp.
#de f i n e CLASS STORE(name , expr ) \
S to r a g eC l a s s< t y p e o f ( expr )> name ( expr )
template <c l a s s Expr>
c l a s s S to r a g eC l a s s
Expr expr ;
pub l i c :
S t o r a g eC l a s s ( const Expr& exp r ) : expr ( e x p r )
. . .
;
Bei Verwendung der vollstandig statischen ET-Variante (Listing 6.2) ist die gesamte Information uber
den Ausdruck bereits in den Template-Typen enthalten. Aus diesem Grund genugt es zum Speichern
von FET-Formeln, den Typ des Ausdrucks mittels eines typedef in einem neuen Typ zu kapseln.
#de f i n e FET STORE (name , expr ) t ypede f t y p e o f ( expr ) name ;
. . .
FET STORE( ex p r t y pe , b + c ∗ d ) ;
Danach kann der definierte Typ expr type bei Klassen oder Funktionen als Template-Parameter einge-
setzt werden. Daruber hinaus kann mit diesem gespeicherten Typen an jeder Stelle ein lokales Objekt
erzeugt werden, da die FET-Objekte keinerlei Parameter bei einem Konstruktoraufruf benotigen.
Listing 7.6: Anwendung von gespeicherten FET-Ausdrucken.
template <c l a s s f o rmu la type >
vo id bar ( f o rmu l a t y pe a ) . . .
. . .
bar ( e x p r t y p e ( ) ) ;
Dadurch lassen sich wiederum Formeln implementieren, indem etwa Handle-Klassen definiert werden,
die auf zentral gespeicherte Daten zugreifen. Dieses Konzept wurde unter anderem in der Bibliothek
Colsamm angewandt, um die Transformationsformeln der Finiten Elemente elegant und effizient pro-
grammieren zu konnen, siehe Abschnitt 11.4.1.
7.3.2 Abspeicherung mittels abstrakter Klassen
Die prasentierte Kapselung von ET-Objekten in Speicherklassen kann jedoch nur angewandt werden,
wenn die Anzahl der zu speichernden Objekte konstant ist, bzw. wenn die Typen der ET-Ausdrucke
bereits bei der Initialisierung der Speicherklasse bekannt sind. Soll jedoch eine dynamische Anzahl
von ET-Ausdrucksbaumen gespeichert werden, so ist es wegen der verschiedenartigen Typen der ET-
Objekte nicht moglich, ein Array fur diese Typen zu definieren. Ebenso konnen ET-Ausdrucke nicht mit
obiger Implementierung in einer bestehenden Klasse gespeichert werden, da der Typ der entsprechenden
Membervariablen beim Initialisieren der Speicherklasse noch nicht bekannt ist.
Da die ET-Objekte also nicht mit ihrem exakten Typ speicherbar sind, muss eine abstrakte Klasse
definiert werden, von welcher die Wrapper-Klasse abgeleitet wird. Dadurch konnen Zeiger der ab-
strakten Basisklasse in den Speicherklassen als Membervariablen benutzt werden, in denen dann die
61
7.3. SPEICHERUNG VON ET
ET-Objekte bei der Zuweisung als neu angelegte Objekte (mittels new) gespeichert werden. Dazu sind
in der Basisklasse, der Wrapper-Klasse und den davon abgeleiteten Klassen virtuelle Destruktoren zu
definieren.
In der Basisklasse sind geeignete Schnittstellen einzufuhren, also virtuelle Funktionen (beispielsweise
zur Auswertung des Ausdrucks), die dann in der Wrapper-Klasse definiert sind. Diese im Wrapper defi-
nierten Funktionen rufen die Auswertung der ET uber den mittels CRTP gespeicherten Ausdruck auf,
und benutzen keine virtuellen Funktionen mehr. Fur eine erfolgreiche Optimierung ist aber darauf zu
achten, dass die neu eingefuhrten Auswertungsfunktionen anders benannt werden als die bestehenden
Auswertungsfunktionen in den Operationsklassen. Ansonsten kann keine der give-Funktionen wegen
der Vererbung von BaseClass mittels Inlining optimiert werden. Definiert man jedoch unterschiedliche
Funktionen wie in Listing 7.7 (give 2 und give), so konnen die Auswertungen wie erwartet optimiert
werden, lediglich am Knoten des Ausdrucksbaumes wird ein virtueller Funktionsaufruf erzeugt.
Listing 7.7: Abspeicherung von ET-Objekten mittels abstrakter Klassen.
s t r u c t BaseC la s s
v i r t u a l double g i v e 2 ( i n t i ) const = 0 ;
v i r t u a l ˜ BaseC la s s ( )
;
template <c l a s s E>
s t r u c t Expr : pub l i c BaseC la s s
. . .
double g i v e 2 ( i n t i ) ) const
r e t u r n s t a t i c c a s t <const E&>(∗ t h i s ) . g i v e ( i ) ;
v i r t u a l ˜Expr ( )
;
. . .
c l a s s S to r a g eC l a s s
BaseC la s s ∗ expr ;
pub l i c :
template<c l a s s E>
vo id sav e ( const E& e )
expr = new E( e ) ;
. . .
;
Abbildung 7.2 zeigt die Laufzeitergebnisse der verschiedenen Arten der Abspeicherung von ET im
Vergleich mit einer direkten Auswertung. Dazu wird als erste Implementierung eine Speicherung der
ET-Objekte uber abstrakte Klassen mit gleicher Auswertungsfunktion in Basisklasse, Wrapper und
Operationsklassen gewahlt. Die zweite Kurve stellt die Abspeicherung uber abstrakte Klassen mit
verschiedenen Auswertungsfunktionen dar, sowie eine Programmierung mittels Typkapselung aus dem
vorherigen Abschnitt 7.3. Die Leistungen der verschiedenen Implementierungen wird fur die Vektortria-
de (6.1) sowie einen großen Ausdruck (6.2) untersucht. Daraus ergeben sich die Performance-Nachteile
der Programmierungen mittels abstrakter Klassen, wobei die deutlichen Verluste fur die Implementie-
rung mit gleichen Auswertungsfunktionen das nicht durchfuhrbare Inlining belegen. Verwendet man
jedoch die Version mit unterschiedlich benannten give-Funktionen, so wird der Leistungsverlust fur
großere Ausdrucke geringer, da nur der erste Aufruf der Auswertung nicht optimierbar ist. Die Abspei-
cherung von ET-Objekten mittels Typkapselung liefert nahezu die gleiche Performance wie die direkte
Auswertung der ET-Ausdrucke.
Die mittels dieser Programmierweise gespeicherten ET-Objekte konnen im weiteren Programmver-
lauf wieder verwendet werden, dazu sind lediglich entsprechende Schnittstellen zu den bestehenden
Datenstrukturen zu implementieren. Dabei ist jedoch zu beachten, dass virtuelle Methoden nicht
von Member-Templates abhangig sein durfen, da der Compiler beim Ubersetzen nicht entscheiden
62
KAPITEL 7. ERWEITERTE METHODEN
,ËNGEDER6EKTOREN
:E
ITP
RO)T
ERA
TIO
N!BSTR+LASSENGLEICHE!USWERTUNGSFKT!BSTR+LASSENVERSCH!USWERTUNGSFKT%4 !BSPEICHERUNGMITELS4YPKAPSELUNG
$IREKTE%4 !USWERTUNG
,ËNGEDER6EKTOREN
:E
ITP
RO)T
ERA
TIO
N
!BSTR+LASSENGLEICHE!USWERTUNGSFKT!BSTR+LASSENVERSCH!USWERTUNGSFKT%4 !BSPEICHERUNGMITELS4YPKAPSELUNG
$IREKTE%4 !USWERTUNG
Abbildung 7.2: Performance-Vergleich der Auswertung von gespeicherten ET-Ausdrucken mit der
direkten Auswertung beim Erstellen des Ausdrucks. Dabei ist ein Leistungsverlust bei der Speicherung
mittels abstrakter Klassen zu erkennen, wobei insbesondere fur Implementierungen mit gleich benannten
Auswertungsfunktionen die Inline-Optimierung der Auswertung komplett fehlschlagt.
kann, welche Funktionen instantiiert werden mussen. Das bedeutet, dass man entweder den Template-
Typen als Template der Basisklasse selbst definiert, oder die Funktionen fur die benotigten Template-
Parameter implementieren muss. Diese sind dann auch in der Wrapper-Klasse einzubauen, nicht aber
in den Operationsklassen, da die Methoden des Wrappers nur die entsprechenden Methoden der ent-
sprechenden erbenden Operationsklasse aufrufen (Listing 7.8).
Die vorgestellten Methoden zur Speicherung von ET-Objekten bieten Moglichkeiten, Ausdrucke uber
eine Zuweisung hinaus zu verwenden. Dabei erzeugt lediglich die Abspeicherung mittels abstrakter
Klasse einen Overhead, der aber bei großen Ausdrucken bzw. bei nicht zu intensiver Nutzung gering
ausfallt. Diese Verschlechterung in der Leistung macht sich jedoch wiederum deutlicher bemerkbar,
wenn verschiedene uber abstrakte Klassen abgespeicherte Ausdrucke miteinander verknupft werden,
da hierdurch mehrere Unterbrechungen in der zu optimierenden Inlining-Kette entstehen.
Listing 7.8: Abspeicherung mit abstrakten Klassen und mehreren Ergebnistypen.
s t r u c t BaseC la s s
v i r t u a l double f oo ( ) const ;
v i r t u a l s t d : : complex<double> f oo ( ) const ;
;
template <c l a s s E>
s t r u c t Expr : pub l i c BaseC la s s
double f oo ( ) const
r e t u r n s t a t i c c a s t <const E&>(∗ t h i s ) . template foo<double >() ;
s t d : : complex<double> f oo ( ) const . . .
;
7.4 Sichere Matrix-Multiplikationen und ausdrucksabhangige
Auswertungen
Nach den bisher prasentierten Methoden zur erweiterten Anwendung der ET-Technik, wird in diesem
Abschnitt ein zunachst nicht offensichtliches Problem von ET-Implementierungen behandelt. Dazu
betrachtet man beispielsweise die Matrix-Vektor-Multiplikation
x = A · x
63
7.4. SICHERE MATRIX-MULTIPLIKATIONEN
mit einem Vektor x und einer Matrix A, in der das Ergebnis also wieder dem gleichen Vektor x
zugewiesen werden soll. Durch den uberladenen Zuweisungsoperator der Vektorklasse wird zunachst
die erste Komponente ausgerechnet und zugewiesen. Doch der zweite Eintrag des Vektors x benotigt
ebenfalls die erste Komponente von x, die nun aber schon neu berechnet ist. In manchen Fallen ist das
Verwenden von Updates zwar erwunscht (z.B. Gauß-Seidel), doch bei der Berechnung von algebraischen
Problemen wird dies zu Fehlern fuhren. Da aber vom Benutzer einer ET-Bibliothek nicht verlangt
werden kann diese Fehlerquellen selbst zu vermeiden, benotigt man in diesen Fallen eine erweiterte
Implementierung im Zuweisungsoperator [DJ03].
Kommt also bei Matrix-Multiplikationen der Ergebnisvektor auch auf der rechten Seite vor – dabei
kann es sich auch um viel kompliziertere Ausdrucke als dem Obigen handeln – muss das Ergebnis
vor der Zuweisung in einem lokalen Vektor gespeichert werden. Der temporare Ergebnisvektor kann
nach der Berechnung des Resultats an den eigentlichen Vektor zugewiesen werden. Dieser zusatzliche
temporare Vektor in der Auswertung wird jedoch nur fur diese Spezialfalle benotigt, in den anderen
Fallen konnen die Berechnungen wie ublich ohne zusatzliche temporare Variablen durchgefuhrt werden.
Erweitert man die Vektorklasse um einen als Member gespeicherten Index, so kann man durch
Abfrage feststellen, ob der Index des Ergebnisvektors auch im Ausdruck der rechten Seite auftaucht. Bei
Implementierungen mittels nummerierten Vektoren existieren solche Indizes bereits, und es kann bereits
zur Ubersetzungszeit festgestellt werden, ob der Vektor der linken Seite auch im Ausdruck vorkommt.
Damit kann je nach dem, ob ein temporarer Vektor benotigt wird, eine gesonderte Implementierung
einfugt werden. Somit erhalt der Anwender in jedem Fall ein korrektes Ergebnis.
Die prasentierten Techniken zur Realisierung einer sicheren Matrix-Multiplikation leiten eine weitere
Nutzung von ET-Objekten ein, namlich die ausdrucksabhangige Auswertung. Hierbei wurde die Ent-
scheidung, welche Auswertungsvariante verwendet werden soll, zur Laufzeit getroffen. Daruber hinaus
besteht aber auch die Moglichkeit, den Zuweisungsoperator fur spezielle Ausdrucke zu uberladen.
Kann anhand des Typs eines ET-Ausdrucksbaumes bereits entschieden werden, welche Auswertungs-
methode angewandt werden soll, so kann dies durch eine zusatzliche uberladende Version des Zuwei-
sungsoperators erreicht werden. Diese wird dann durch das pattern matching zur Ubersetzungszeit fur
die entsprechenden Ausdrucke ausgewahlt.
Dies kann vor allem bei komplexeren Anwendungen der ET-Implementierung benutzt werden, um in
bestimmten Fallen eine performantere Auswertung der Ausdrucke zu erreichen. Betrachtet man dazu
die Verwendung von ET zur numerischen Losung partieller Differentialgleichungen, so sind bestimmte
Ausdrucke mittels einer gesonderten Auswertungsroutine schneller berechenbar als uber den herkomm-
lichen Weg – beispielsweise eine komplette Implementierung des Laplace-Operators im Vergleich zur
Summe der zweiten Ableitungen. Dies kann besonders auf Hochleistungsrechnern zu entscheidenden
Unterschieden in der Performance fuhren.
Durch das entsprechende Uberladen des Zuweisungsoperators konnen auch verschiedene ET-Im-
plementierungen parallel verwendet werden. Betrachten wir dazu beispielsweise die Verwendung von
vektorbasierten Datenstrukturen zur Diskretisierung von Funktionen auf zweidimensionalen Gebie-
ten. Die Berechnungen mit den Vektoren konnen dabei wie gewohnt komponentenweise durchgefuhrt
werden. Doch zur benutzerfreundlichen Initialisierung der Vektoren bzgl. des Diskretisierungsgebiets
kann eine zusatzliche ET-Implementierung eingefuhrt werden, welche durch Polynome oder trigonome-
trische Funktionen Initialisierungsausdrucke beschreibt, siehe auch [Pfl01]. Fur diese gesonderte ET-
Programmierung wird ein eigener Zuweisungsoperator mit passender Auswertungsroutine definiert. Die
notige Entscheidung, welche Zuweisung fur den aktuellen Ausdruck benotigt wird, kann der Compiler
dann anhand des Datentyps zur Ubersetzungszeit treffen.
Die prasentierten Methoden zum erweiterten Einsatz der Technik eroffnen ein weites Anwendungs-
gebiet fur ET, auch als flexible Bibliothek zum Einsatz fur verschiedene Datentypen. Der Template-
Mechanismus mit der internen Codegenerierung nimmt dem Programmierer dazu einen Großteil der
notigen Implementierungsarbeit zur Ubersetzungszeit ab.
64
KAPITEL 8. KOMPILIERZEITEN VON ET
8 Kompilierzeiten von ET
Die Leistungsfahigkeit von ET-Bibliotheken basiert auf den Optimierungen der C++-Compiler, die
vorwiegend aus der durchgangigen Typinformation der ET-Objekte resultiert. Grundsatzlich war der
Template-Mechanismus in C++ bei seiner Einfuhrung jedoch nicht fur eine derart intensive Nutzung
wie durch ET ausgelegt. Dennoch haben sich die Compilerbauer im Laufe der Zeit auf die wachsen-
de Komplexitat der Template-Konstruktionen eingestellt. Betrachtet man beispielsweise verschiedene
GNU-C++-Versionen, so ist eine (fast) stetige Verkurzung von ET-Ubersetzungszeiten zu verzeichnen.
Dennoch compilieren ET-Programme immer noch deutlich langer als vergleichbare Implementierungen
ohne ET. Da diese Zeiten fur das Compilieren von ET-Bibliotheken teilweise immens sind, werden in
vorliegenden Kapitel die Ubersetzungsdauern von ET untersucht und Programmierkonzepte prasen-
tiert, welche eine schnellere Compilierung von ET-Implementierungen ermoglichen.
8.1 Ubersetzung von ET-Programmen
Ein Großteil der fur die effiziente Berechnung von ET-Ausdrucken benotigten Optimierung wird durch
das Inlining der Auswertungsfunktionen ermoglicht. Dazu muss der Compiler mehrfach einen Pro-
grammcode erzeugen, der nach abgeschlossener Optimierung entsprechend eingesetzt wird, und da-
nach nicht mehr weiter verwendet werden kann. Doch da es sich bei den ET-Implementierungen meist
um kleine Inline-Funktionen handelt, verursachen diese Optimierungen nur einen kleinen Teil der re-
sultierenden Ubersetzungszeiten. Auch nicht-optimierte Ubersetzungen von ET-Programmen dauern
unerwartet lange, meist nur wenig kurzer als die vollstandig optimierte Compilierung. Einige Unter-
suchungen von optimierten Ubersetzungen mittels Profiling-Tools ergaben, dass bei den betrachteten
GNU- bzw. Intel-Compilern die Zeiten fur die Optimierung weniger als 15% der gesamten Uberset-
zungsdauer betrugen. Der eigentliche Aufwand beim Compilieren entsteht eher durch die geschachtelten
Template-Typen.
Betrachtet man nun die Compilierungszeiten verschiedener ET-Anwendungen, so lasst sich zunachst
feststellen, dass die Dauer der Ubersetzungen mit wachsender Tiefe der Ausdrucksbaume superlinear
zunimmt, siehe auch [MS00]. Prinzipiell dienen ET zur Generierung des anwendungsbezogenen Codes,
was zu weitaus komplexeren Programmen fuhrt, als man zunachst erwarten wurde. Durch die Ver-
wendung von Code-To-Code-Translatoren wie etwa ROSE [Qui02], kann der Programmcode mit allen
notigen Instantiierungen erzeugt und als Zwischencode gespeichert werden. Ubersetzt man nun diesen
Zwischencode, fur den der Compiler keine weiteren Instantiierungen mehr selbst generieren muss, so
reduziert sich die Ubersetzungszeit im Vergleich zum ursprunglichen Code nur dann, wenn die jeweils
aktuell benotigten Instantiierungen bereits bestehen. Steht dagegen die großte Instantiierung als erstes
beim Compilieren an, so dauert die Ubersetzung so lange wie bei dem ursprunglichen Programmcode
ohne explizite Instantiierungen. Grund dafur ist die Erzeugung der Templates, die fur die benotig-
ten ET-Ausdrucke rekursiv erfolgen muss, bis der Ubersetzer auf eine Instantiierung trifft, die bereits
existiert.
Bei einer Untersuchung der Compilierungszeiten diverser ET-Implementierungen konnen jedoch fur
alle getesteten Compilern ET-Varianten bestimmt werden, die deutlich langer compilierten als die
ubrigen. Die Ubersetzung von ET-Implementierungen mit vollen Objekten als Member-Variablen in
den Operationsklassen (Listing 5.3) dauert merklich langer als die Compilierung der gleichen Ausdrucke
mittels der anderen ET-Versionen.
65
8.2. PRAKTISCHE METHODEN ZUR SCHNELLEREN COMPILIERUNG
Dazu zeigt die linke Grafik der Abbildung 8.1 die Ubersetzungszeiten der ursprunglichen Version
von Veldhuizen und der in Kapitel 5 prasentierten ET-Implementierungen mit vollen Objekten bzw.
mit Referenzen, jeweils compiliert mit dem GNU-Compiler, Version 4.1.0. Betrachtet wird dabei die
Summe von Vektoren mit komponentenweiser Auswertung
a = a + a + a + . . . + a,
welche einen linearen Ausdrucksbaum erzeugt. Die Graphik enthalt die Ubersetzungszeiten bzgl. wach-
sender Ausdruckgroßen. Die schnell ansteigenden Ubersetzungszeiten der ET mit vollen Memberob-
jekten ist auf die rekursive Bestimmung der Typgroßen zuruckzufuhren. Dies ist bei Referenzen als
Membervariablen nicht notig, da diese immer die gleiche Große besitzen.
,ËNGEDERKOMPILIERTEN!USDRàCKE
¯B
ERSE
TZU
NG
SZEI
TIN
3EK
%4 6ERSIONVON6ELDHUIZEN%4MITVOLLEN/BJEKTENIN/PERATIONSKLASSEN%INFACHE%4 6ARIANTEMIT2EFERENZEN
,ËNGEDERKOMPILIERTEN!USDRàCKE
¯B
ERSE
TZU
NG
SZEI
TIN
3EK
# #ODEOHNE%4
.AMESPACE %4OHNE7RAPPER%INFACHE%4 6ERSION
&%4 )MPLEMENTIERUNG
Abbildung 8.1: Ubersetzungszeiten des verschiedener ET-Implementierungen mit dem GNU-C++-
Compiler, Version 4.1.0. Die linke Grafik zeigt die Compilierungszeiten von ET-Varianten mit vollen
Objekten als Membervariablen in den Operationsklassen, wobei die Programmierungen von Veldhuizen,
aus Listing 5.3 mit der einfachen Programmierung mit Referenzen verglichen werden. Letztgenannte
Implementierungsvariante wird in der rechten Grafik den Versionen ohne Wrapperklasse, FET und
C-Code gegenubergestellt.
Daneben vergleicht die rechte Grafik die Compilierungszeiten der eben betrachteten einfachen ET-
Variante mit Referenzen, mit der namespace-gekapselten ET-Version, den FET und der Implementie-
rung im C-Stil. Dabei compiliert die namespace-gekapselte Version schneller als die einfache ET, da
sie keine Wrapperklasse besitzt, die ja fur jedes ET-Objekt instantiiert werden muss. Noch schneller
konnen jedoch die FET-Implementierung ubersetzt werden, da sie keinerlei Membervariablen oder Kon-
struktoren besitzt. Naturlich kann eine Implementierung im C-Stil am schnellsten ubersetzt werden,
da hier keinerlei Templates verwendet werden.
Ubersetzungen mit dem Intel-C++-Compiler liefern sehr ahnliche Ergebnisse, doch ubersetzt dieser
die Template-Codes prinzipiell schneller als die GNU-Compiler. Nach diesen allgemeinen Betrach-
tungen werden noch spezielle Implementierungsmechanismen prasentiert, mittels denen die Compi-
lierungszeiten – vorwiegend der GNU-Compiler – verkurzt werden konnen.
8.2 Praktische Methoden zur schnelleren Compilierung
Wie bereits an den Grafiken 8.1 in vorherigen Abschnitt deutlich wurde, steigt die Ubersetzungszeit bei
allen ET-Implementierungen uberlinear an, wenn auch unterschiedlich stark. Dies ist auf die Anzahl
und Komplexitaten der jeweils verwendeten Template-Klassen zuruckzufuhren, fur die der Compiler
nach einer Instantiierung auch entsprechende semantische Analysen durchfuhren muss. Fur das Erzeu-
gen des Codes und den anschließenden Uberprufungen werden beim Ubersetzen die entsprechenden
66
KAPITEL 8. KOMPILIERZEITEN VON ET
Instantiierungslisten nach bereits generierten Template-Klassen durchsucht. Die dazu notigen Verglei-
che der rekursiven Template-Listen beeintrachtigen die Ubersetzungszeit zusatzlich.
Im Folgenden werden mehrere Methoden zur Verkurzung der Compilierungsdauern von ET-Pro-
grammen zusammengefasst. Die prasentierten Techniken haben einzeln betrachtet nur eine geringe
Beschleunigung der Ubersetzungszeiten zur Folge, so dass sie durch keine zusatzlichen Performance-
Kurven belegt werden. Auch hangen die Erfolge der einzelnen Methoden vom verwendeten Compiler
und der jeweiligen Computerplattform ab.
8.2.1 Template-Anordnung in den Operationsklassen
Wie bereits in Kapitel 4.2.3 angesprochen gibt es zwei grundsatzliche Moglichkeiten die Operationsklas-
sen zu implementieren. Die bisher zuruckgestellte Variante definiert allgemeine unare und binare Ope-
rationsklassen mit je einem zusatzlichen Template-Parameter fur eine Klasse, welche die auszufuhrende
Operation kapselt (Listing 4.3).
template <c l a s s A, c l a s s Op, c l a s s B>
c l a s s B ina r y Exp r ;
Dabei ist es fur die Programmierung zunachst vollig irrelevant, in welcher Reihenfolge die Template-
Parameter angeordnet werden. Da die jeweiligen Template-Listen bei der Suche nach bestehenden
Instantiierungen in der vorgegebenen Reihenfolge durchlaufen werden, fuhrt die obige Definition der
Klasse Binary Expr dazu, dass zuerst die Typen des linken Operanden verglichen werden. Doch eine erste
einfache Unterscheidung, ob die aktuellen Templates mit bereits bestehenden ubereinstimmen ergibt
sich schneller aus dem Typ der Operation. Deshalb ist es sinnvoller, die Operation als ersten Template-
Parameter zu behandeln, und erst danach die Typen der Operanden. Dadurch wird zunachst der Typ
der Operation verglichen, welcher kein geschachtelter Typ ist – oder, zumindest nicht in dem Maße
wie die Operanden. Bei den Implementierungen mit separaten Klassen fur jede Operation tritt diese
Problematik nicht auf.
template <c l a s s Op, c l a s s A, c l a s s B>
c l a s s B ina r y Exp r ;
Zum Test, wie abhangig der verwendete Compiler von der Organisation der Template-Listen ist,
kann der Programmcode aus Listing 8.1 herangezogen werden. Darin werden zwei prinzipiell gleich
strukturierte Klassen (C Int bzw. Int C) prasentiert, die sich lediglich in der Reihenfolge von Integer
bzw. geschachteltem Template-Typ unterscheiden. Intern wird rekursiv ein Typ bestimmt, welcher
jedoch immer int ergibt.
Bei der Generierung einer Instantiierung entstehen fur das Klassen-Template C geschachtelte Typen
von C Int, die jedoch fur jede Instanz einen eigenen Template-Integer besitzen. Fur beide Versionen
werden bei den selben Template-Parametern die gleiche Anzahl an Instantiierungen erzeugt. Mittels
template c l a s s C Int<i n t ,500 > ;
kann man sich zu gegebenen Template-Parametern die notigen Instantiierungen generieren lassen.
Die resultierende Ubersetzungszeit ist fur diese Instantiierung (deutlich) langer als fur den analogen
Ausdruck der zweiten Implementierung, die zuerst den Integer als Template-Parameter besitzt:
template c l a s s I n t C <500 , i n t >;
Dabei fallt diese Differenz je nach verwendetem Compiler verschieden groß aus, abhangig davon, wie
effizient im Fall von C Int die rekursiven Templates verarbeitet werden konnen.
67
8.2. PRAKTISCHE METHODEN ZUR SCHNELLEREN COMPILIERUNG
Listing 8.1: Programmcodes mit vertauschten Template-Parametern.
template <c l a s s C, i n t I>
s t r u c t C I n t
t ypede f typename C Int<C Int<C, I >, I −1>::TYPE TYPE ;
;
5
template <c l a s s C>
s t r u c t C Int<C,0>
t ypede f i n t TYPE ;
;
10
template < i n t I , c l a s s C>
s t r u c t I n t C
t ypede f typename I n t C<I −1, I n t C<I , C> > : :TYPE TYPE ;
;
15
template <c l a s s C>
s t r u c t I n t C <0,C>
t ypede f i n t TYPE ;
;
8.2.2 Balancierung der Ausdrucksbaume
In den Beispielen aus Abschnitt 8.1 wurden mit der Summe von Vektoren spezielle Probleme betrach-
tet, die zu einem linearen Ausdrucksbaum fuhren. In allgemeinen Anwendungen wird man automa-
tisch balancierte Ausdrucke haben, beispielsweise durch Multiplikation oder durch eine Klammerung.
Verursacht eine Implementierung mit großen ET-Ausdrucken eine lange Ubersetzungszeit, so kann
man diese meist durch eine passende Klammerung verkurzen. Dennoch konnen sich bei mehreren
ET-Anwendungen in einem Programm durchaus viele verschiedene Instantiierungen ergeben, so dass
hierfur die Compilierungszeit durch Klammerung kaum verkurzt werden kann.
Listing 8.2: Einfache, schneller ubersetzbare ET-Implementierung.
1 template< i n t iA , c l a s s A>
s t r u c t X
ope ra to r const A&() const r e t u r n ∗ s t a t i c c a s t <const A∗>( t h i s ) ;
;
template < i n t iL , i n t iR , c l a s s L , c l a s s R>
6 c l a s s Add : pub l i c X<i L+iR , Add<iL , iR , L ,R> >
const L& l ; const R& r ;
pub l i c :
Add( const L& l , const R& r ) : l ( l ) , r ( r )
double g i v e ( i n t i ) const r e t u r n l . g i v e ( i )+ r . g i v e ( i ) ;
11 ;
template < i n t iL , i n t iR , c l a s s L , c l a s s R>
i n l i n e Add<iL , iR , L ,R>
ope ra to r+(const X<iL , L>& l , const X<iR ,R>& r )
r e t u r n Add<iL , iR , L ,R>( l , r ) ;
16
c l a s s Vec : pub l i c X<1,Vec>
. . .
template< i n t iA , c l a s s A>
vo id ope ra to r=(const X<iA ,A>& x )
21 const A& x ( x ) ;
f o r ( i n t i= 0 ; i < n ; ++i )
da ta [ i ] = x . g i v e ( i ) ;
double g i v e ( i n t i ) const r e t u r n data [ i ] ;
26 ;
68
KAPITEL 8. KOMPILIERZEITEN VON ET
Fur solche intensiven ET-Anwendungen wird eine von Listing 5.3 abgewandelte Implementierung
prasentiert, mittels derer die Compilierungszeiten reduziert werden konnen. Listing 8.2 zeigt eine Vari-
ante, die zusatzlich zu jedem Template-Parameter, der einen geschachtelten Typen enthalten kann, ein
Integer-Template vorangestellt, der beim Durchsuchen der bestehenden Instantiierungen zuerst ver-
glichen wird. Das Template-Integer der Wrapper-Klasse wird fur Vektoren immer 1 gesetzt, bzw. bei
Operationsklassen gleich der Summe der Template-Integer der Operanden. Dadurch ergeben sich je
nach Tiefe der Operanden unterschiedliche Integer-Werte, so dass der Compiler die Instantiierungen
nur bei Ubereinstimmung dieses Integers durchsucht. Es ware anschaulicher anstatt der Summe das
Maximum zu bestimmen, doch fuhren die dazu notigen Vergleiche zu einer langeren Ubersetzungszeit.
Diese zusatzlichen Template-Integer beeintrachtigt die notwendige Zeit fur das Ubersetzen normaler
Anwendungen nicht.
8.2.3 Auslagerung von ET-Ausdrucken
Falls die Ubersetzungszeiten trotz Anwendung dieser Methoden dennoch zu lange sind, so kann man
als Anwender einer ET-Bibliothek versuchen, Teile der Berechnungen mittels ET in eine Funktion aus-
zulagern. Betrachten wir beispielsweise die Implementierung des cg-Algorithmus’ zur Berechnung der
Losung eines linearen Gleichungssystems. Verlagert man diesen Algorithmus in eine eigene Funktion
in einer separaten Datei und ubergibt der Funktion die notigen Variablen (Vektoren, Matrix, Ab-
bruchkriterien), so kann das cg-Verfahren getrennt von der eigentlichen Anwendung ubersetzt werden.
Dadurch muss die cg-Implementierung in der separaten Ubersetzungseinheit nicht bei jeder Anderung
des restlichen Anwednungscodes neu compiliert werden.
vo id cg a l go r i t hm ( Vector& x , const Matr i x& A,
const Vector& b , double e p s i l o n ) ;
Diese Methode lasst sich nur dann so einfach anwenden, wenn die Vektoren nicht selbst von Template-
Parametern abhangen. In diesem Fall mussten beim Compilieren der separaten Funktion die Instan-
tiierungen aller benotigten Typen durch explizite Instantiierung generiert werden. Sollen jedoch gan-
ze ET-Objekte ubergeben werden, kann eine Auslagerung in eine separate Compilierungseinheit nur
mittels einer abstrakten Basisklasse, wie in Listing 5.4, realisiert werden. Denn hierdurch ist die Ar-
gumentliste der Funktion unabhangig von Template-Parametern. Dies beeintrachtigt jedoch wiederum
die Leistung des resultierenden Programmcodes.
Prinzipiell existieren also viele kleine Implementierungsvarianten, mittels derer die Ubersetzungs-
zeiten von ET-Ausdrucken verkurzt werden konnen. Dies betrifft den Programmierer ebenso wie die
Anwender von ET-Biliohteken.
69
8.2. PRAKTISCHE METHODEN ZUR SCHNELLEREN COMPILIERUNG
70
KAPITEL 9. ET – AUSBLICK
9 ET-Bibliotheken – Ausblick
Die Implementierungstechnik der Expression Templates ist eine hervorragende Moglichkeit effiziente
Programme unter Vermeidung von unnotigen temporaren Variablen zu realisieren. Nach ihrer Einfuhr-
ung und Verbreitung wurde die ET-Technik vor allem fur die effiziente Programmierung von benutzer-
freundlichen Schnittstellen in C++ herangezogen. Diese intensive Nutzung des Template-Mechanismus
fuhrte auch zu leistungsfahigeren Compilern, durch welche die ET-Konstrukte schneller und auch per-
formant ubersetzt und optimiert werden konnen.
Wahrenddessen wurden aber auch die noch bestehenden Mangel der Methode aufgedeckt, wie zum
Beispiel das Aliasing-Problem oder die komplizierte und oft langwierige Programmierung. Um den
Programmierern diese aufwandige Programmierung abzunehmen, wurde mit PETE eine Bibliothek
entwickelt, welche die ET-Implementierungen fur array-basierte Datenstrukturen vereinfachte. Doch
ET-Bibliotheken fur komplexere oder aufwandigere Datenstrukturen waren damit nur sehr schwer bis
gar nicht zu realisieren. Aus diesem Grund wurde mit der Entwicklung der einfachen ET eine Pro-
grammierweise eingefuhrt, mittels derer auch darin ungeubte Programmierer sehr intuitiv und ohne
großen Implementierungsaufwand die Technik fur ihre Problemstellungen verwenden konnen. Daruber
hinaus wurde mit den Fast Expression Templates eine Variante prasentiert, welche ein Auflosen des
Aliasings bei wiederholtem Auftreten von Variablen in Ausdrucken ermoglicht und so effiziente Pro-
gramme liefert. Dies macht die Implementierungstechnik, wie gezeigt, besonders auch fur Vektorrechner
und andere parallele Architekturen interessant, da die Effizienz der Programme nahezu identisch mit
der entsprechenden Programmierung in C ist. Damit erlauben FET ein effizientes objekt-orientiertes
Implementieren von Programmen auf Hoch- und Hochstleistungsrechnern.
9.1 Limitierungen von ET-Bibliotheken
Obwohl die Implementierung von ET-Programmen durch die prasentierten Methoden einfacher durch-
fuhrbar ist, konnen dennoch bei der Anwendung von ET-Bibliotheken unerwunschte Probleme entste-
hen.
9.1.1 Komplexe Fehlermeldungen
Anders als bei Bibliotheken mittels abstrakter Klassen und virtuellen Funktionen, bei denen eine falsche
Benutzung eine kurze Fehlermeldung zur Folge hat, entstehen auch bei wohlgetesteten ET-Programmen
durchaus komplexe Fehlermeldungen. Um diese zu verstehen und die Probleme in der Anwendung zu
beheben, benotigt man meist ein gewisses Verstandnis fur die verwendete Bibliothek.
Benutzt man versehentlich fur einen ET-Ausdruck Operatoren oder Funktionen, die nicht entspre-
chend uberladen sind, so wird eine umfangreiche Fehlermeldung uber die fehlende Funktion ausgege-
ben. Der Compiler listet – als Information zur besseren Fehlerbehebung – die Typen des fehlerhaf-
ten Ausdrucks auf. Und da es sich dabei selbst um geschachtelte Typen handeln kann, konnen diese
Ausdrucke sehr komplex werden. Doch dadurch, dass die in den Kapiteln 5 und 6 prasentierten ET-
Implementierungen keine unnotig mittels eines Wrappers gekapselte Ausdrucke besitzt, fallen diese
Fehlermeldungen kurzer aus als bei den ursprunglichen ET-Versionen.
Daneben ergeben sich noch viel kompliziertere Fehlermeldungen, falls die falsche Benutzung der
Bibliothek erst bei denen als inline deklarierten Funktionsaufrufen auftritt. Denn dann gibt der Com-
71
9.1. LIMITIERUNGEN VON ET-BIBLIOTHEKEN
piler alle Instantiierungen des Funktionsaufrufs aus, die zu diesem Fehler gefuhrt haben, jedesmal mit
der kompletten Angabe aller aktueller Template-Parameter.
Fur eine gut anwendbare ET-Bibliothek sollten deshalb die Compiler-Fehler so weit es geht durch
Laufzeit-Fehler abgefangen werden. Im Ansatz wurde dies in Abschnitt 7.2 vorgefuhrt. Doch auf diesem
Weg kann nur ein gewisser Teil der Anwendungsfehler mittels Laufzeitfehlern abgehandelt werden.
Außerdem sollte ein Anwender uber einen fehlerhaften Code bereits beim ersten Erkennen informiert
werden, nicht erst wahrend des Programmlauf.
Prinzipiell konnen auch eigene C++-Parser implementiert werden, um einfachere Ubersetzerfeh-
lermeldungen zu erhalten, beispielsweise durch Elsa [McP04]. Mittels diesem auf dem GLR Parser
Generator Elkhound [MN04] aufbauenden C++-Parser werden u. a. Template-Implementierungen auf
Korrektheit uberpruft und bei Fehlern verstandlichere Meldung generiert, die nur die fur den Anwen-
der notigen Informationen zum Beheben des Fehlers enthalten. Doch diese mussen dann zusatzlich
zur eigentlichen Bibliothek angeboten und benutzt werden, was die Anwendung der ET-Programme
erschwert.
9.1.2 Testbarkeit von ET-Programmen
Da die ET-Implementierungen auf den Fahigkeiten des Template-Mechanismus beruhen, ergeben sich
auch die gleichen Probleme der Testbarkeit wie bei Template-Programmen. Der Compiler uberpruft
den templatisierten Code zunachst nur syntaktisch und kann eine semantische Analyse erst bei ent-
sprechenden Instantiierungen durchfuhren. Da der Compiler den templatisierten Code aber nur dann
instantiiert, wenn dieser benotigt wird, konnen fehlerhafte Implementierungen lange unentdeckt blei-
ben. Diese entstehen dann erst bei der ersten Benutzung des entsprechenden Programmteils.
Zur sorgfaltigen Entwicklung von ET-Bibliotheken sollten deshalb parallel zur Implementierung
auch die zugehorigen Testprogramme erstellt werden, welche den aktuell entwickelten Programmco-
de ausfuhren. So kann leichter sichergestellt werden, dass Teile der templatisierten Programmierung
ungetestet bleiben. Dabei muss darauf geachtet werden, dass Memberfunktionen mit Templates nicht
automatisch mit der Klasse generiert werden, sondern wiederum nur dann, wenn speziell diese Funktion
benotigt wird. Mit entsprechenden Testcodes konnen die potentiellen Fehler in ET-Implementierungen
zumindestens reduziert werden.
9.1.3 Grenzen der Optimierung
Die effizienten Programme die mittels ET erreicht werden konnen, resultieren uberwiegend aus den
Inline-Optimierungen der C++-Compiler, die wiederum nur durch die vollstandige und exakte Ty-
pinformation der ET-Objekte zur Ubersetzungszeit durchfuhrbar sind. Wie bei der Speicherung von
ET-Ausdrucken (Abschnitt 7.3) deutlich wurde, kann nur bei der durchgangigen Sicherung des ET-
Typs die ursprungliche Performance der direkten Auswertung erreicht werden. Sobald Teile dieser
Typinformation fehlen, verschlechtert sich auch die Effizienz der resultierenden Programme, da kein
vollstandiges Inlining mehr moglich ist.
Daruber hinaus konnen die ET-Ausdrucke nur durch die Definition neuer Variablen oder Typen ge-
speichert, und somit uber die Grenzen des semantischen Ausdrucks weiter verwendet werden. Daneben
sind keine ET-spezifischen Optimierungen von Objekten uber die Grenzen von Programmausdrucken
moglich. Da das Semikolon (;) sowie die geschweiften Klammern () nicht uberladbar sind, konnen
auch keine entsprechenden Optimierungen durch weitere ET-basierte Implementierungen realisiert wer-
den. Somit sind benutzerfreundliche, ausdruckubergreifende Optimierungen nur mit Einschrankungen
moglich, siehe dazu auch Abschnitt 12.2.
72
KAPITEL 9. ET – AUSBLICK
9.2 Erweiterte Anwendungsgebiete
Neben den bereits angesprochenen Moglichkeiten einer Benutzung der ET-Technik fur array-basierte
Datenstrukturen, existieren noch weitere interessante Anwendungsgebiete, die im Folgenden kurz be-
trachtet werden.
• FACT! (Functional Additions to C++ through Templates and Classes) [SS00] ist eine mittels
PETE erzeugte ET-Bibliothek zur Implementierung des Lambda-Kalkuls. Daher konnen Funk-
tionen mit der Definition entsprechender Variablenlisten in neuen Funktionen gekapselt werden,
die dann wiederum durch Einsetzen fester Argumentlisten ausgewertet werden. Ein einfaches Bei-
spiel ist die Lambda-Funktion zur Addition von zwei Werten, die in FACT! wie folgt geschrieben
werden kann, wobei a und b beliebige Objekte mit definiertem Plus-Operator sein konnen:
us ing LAMBDA: : x ;
us ing LAMBDA: : y ;
. . .
lambda ( x , y , x + y ) ( a , b ) ;
Hierbei wird die ET-Technik also zur Einfuhrung von Funktionen herangezogen, in die dann bei
der Auswertung die entsprechenden Variablen eingesetzt werden konnen.
• Eine weitere interessante Anwendung stellt die Verwendung von ET-Implementierungen zur Dif-
ferentialrechnungen von Funktionen dar. Werden die Funktionsteile (Polynome, trigonometrische
Funktionen, etc.) in Typen gekapselt, so konnen fur die, durch zugehoriges Operatoruberladen
entstehenden, ET-Ausdrucke Ableitungsregeln implementiert werden [GG]. Die Berechnungen der
Ableitungsformeln konnen durch entsprechende Typumwandlungen bereits zur Ubersetzungszeit
durchgefuhrt werden. Ahnliche und weiterfuhrende Konzepte wurden auch in Colsamm realisiert,
siehe Kapitel 11.
Daruber hinaus lassen sich ET auch noch fur die Realisierung von Schleifeniterationen heranziehen,
oder etwa zur Programmierung von Mengenalgebren (Schnitt, Vereinigung, Komplement), siehe dazu
Kapitel 12.
9.3 Abschließende Bemerkungen
Die effiziente Implementierung der mathematischen Operatoren in C++ wurde durch die Einfuhrung
der Expression Templates revolutioniert. Durch die Kapselung von Ausdrucksbaumen in templatisierten
Klassen wird ein vollstandiges Inlining zur Ubersetzungszeit ermoglicht. Die Effizienz der resultierenden
Implementierungen liegt bereits sehr nahe an der Performance vergleichbarer Programme im C-Stil.
Fur die Entwicklung von ET-Bibliotheken wurden in der vorliegenden Arbeit Implementierungsvari-
anten prasentiert, die eine einfache, kurze Programmierung der Technik zulassen. Dabei werden die zu
implementierenden Template-Konstrukte klein gehalten, so dass undurchsichtige Fehlerquellen redu-
ziert werden. Trotzdem ist dadurch weiterhin ein typsicheres Uberladen der Operatoren gewahrleistet,
so dass keine Konflikte mit bestehenden Versionen von Operatoren entstehen konnen. Dadurch besteht
auch fur Programmierer ohne tiefere Template-Kenntnisse die Moglichkeit, die ET-Technik einfach und
intuitiv implementieren.
Zusatzlich prasentiert die vorliegende Arbeit, mit den nummerierten Variablen und den Fast Ex-
pression Templates, Programmiervarianten, mit denen die bestehenden Performance-Mangel der ET-
Technik behoben werden. Auf Grund des konservativen Mechanismus zum Auflosen der Aliase, konnen
die Compiler nicht alle erforderlichen Identifizierungen der Datenzeiger durchfuhren. Durch die Zuord-
nung der Zeiger zu eigenen Klassentypen und der dadurch moglichen Deklaration als statische Zeiger,
optimieren die Compiler die ET-Anwendungen wie gewunscht. Dies macht den Einsatz der ET-Technik
73
9.3. ABSCHLIESSENDE BEMERKUNGEN
auch fur Hoch- und Hochstleistungsrechner interessant, um effiziente und benutzerfreundliche Biblio-
theken ohne Performance-Verluste anwenden zu konnen.
Die Nutzung der ET-Technik benotigt eine effektive und robuste Realisierung des Template-Mecha-
nismus. Obwohl diese intensive Verwendung von Templates in C++ ursprunglich nicht beabsichtigt
war, sind die ET-Anwendungen mit den mehrfach geschachtelten Typen dennoch ohne nennenswer-
te Einschrankungen ubersetzbar. Jedoch offenbaren viele Compiler bei extremem Einsatz von ET-
Implementierungen durchaus Grenzen (siehe Abschnitt 8). Daneben verdeutlichen die bestehenden
Performance-Mangel der ET-Technik und der daraus entstandenen FET-Implementierungen die Opti-
mierungslucken der C++-Compiler. Die prasentierten Ergebnisse konnen dazu dienen, den Compiler-
bauern Hinweise fur eine verbesserte Optimierung zu bieten.
Generell stellen Expression Templates eine sehr machtige und interessante Implementierungstechnik
zum Erstellen und effizienten Auswerten von Ausdrucksbaumen dar. Die prasentierten Mechanismen
dienen als Ermutigung fur Entwickler wissenschaftlicher Bibliotheken eigene ET-Implementierungen
einzusetzen, um so die Benutzung und Anwendungssicherheit ihrer Codes zu verbessern.
74
Teil III
Einsatz von ET zum Losen von
partiellen Differentialgleichungen
KAPITEL 10. FINITE ELEMENTE
10 Finite Elemente
Viele Vorgange der Natur die in Physik, Chemie oder Biologie modelliert werden, werden durch (par-
tielle) Differentialgleichungen beschrieben. Dabei sind grundsatzlich Funktionen gesucht, welche die
entsprechende Differentialgleichung zu gegebenen Anfangs- und/oder Randwerten erfullen. Einige be-
kannte Beispiele von Differentialgleichungen sind:
• Poisson-Gleichung, z.B. bei der Warmeausbreitung in einer Platte [Bra97],
• Navier-Stokes-Gleichungen, durch die die Stromungsgeschwindigkeit und die zugehorige Druck-
verteilung in sog. Newtonschen Gasen oder Flussigkeiten beschrieben werden [KA00], oder
• Maxwell-Gleichungen, mit denen die Erzeugung und die Wechselwirkung von elektrischen und
magnetischen Feldern bzw. deren Ladungen und Stromen modelliert werden [Mon03].
Ganz allgemein lasst sich eine PDG der Ordnung k in der impliziten Form
F(
x, u(x), Du(x), D2u(x), . . . , Dku(x), . . .)
= 0
darstellen, worin F eine beliebige Funktion ist, x ∈ Rd gilt und Dk die partiellen Ableitungen bis zum
Grad k bezeichnen.
Im Rahmen dieser Arbeit beschranken wir die Betrachtung auf explizite lineare PDGen 2. Ordnung,
d.h. auf Gleichungen, in denen nur Differentialoperatoren des ersten und zweiten Grades auftreten,
also [Bra97]
−n
∑
i,k=1
∂
∂xi
(
aik(x)∂
∂xku
)
+
n∑
i=1
bi(x)∂u
∂xi+ c(x)u = f(x), (10.1)
wobei a : Ω → Rd,d, b : Ω → Rd und c : Ω → R. In kurzerer Schreibweise lautet (10.1) wie folgt:
−∇ · (a(x)∇u) + b(x) · ∇u + c(x)u = f(x).
Je nach den Eigenschaften der Matrix a(x) werden die Differentialgleichungen (10.1) in drei Typen
eingeteilt:
• ist a(x) in jedem Punkt positiv definit, so heisst (10.1) elliptisch,
• hat a(x) einen negativen und n − 1 positive Eigenwerte, wird (10.1) hyperbolisch genannt, und
• ist a(x) in jedem Punkt semidefinit und rg(a(x), b(x)) = n, so heißt (10.1) parabolisch.
Im elliptischen Fall schreibt man kurz
Lu = f. (10.2)
Hyperbolische bzw. parabolische PDGen konnen oft (mit t als Zeitkomponente) folgendermaßen ge-
schrieben werden (L ist ellipitischer Differentialoperator):
∂2u
∂t2+ Lu = f bzw.
∂u
∂t+ Lu = f.
Da sich die Losungen solcher Differentialgleichungen nur in speziellen Fallen analytisch berechnen
lassen, greift man im Allgemeinen auf numerische Losungsverfahren zuruck. Fur hyperbolische und
77
10.1. DIE FINITE ELEMENTE METHODE
parabolische PDGen werden dazu meist spezielle Diskretisierungen fur die Zeitkomponente verwendet,
z.B. explizit/implizites Euler-Verfahren oder Runge-Kutta-Verfahren. Deshalb beschranken sich die
weiteren Erlauterungen auf den elliptischen Fall.
Eine korrekt gestellte elliptische PDG 2. Ordnung besitzt zusatzlich vorgegebene Randbedingungen.
Prinzipiell unterscheidet man drei unterschiedliche Typen von Randbedingungen, namlich Dirichletsche
(D), Neumannsche (N) und Robinsche (R) Randbedingungen, die zusammen den Rand des Gebietes
Ω in disjunkte Mengen aufteilen,
δΩ = ΓD ∪ ΓN ∪ ΓR.
Dabei ist ΓD abgeschlossen. Des Weiteren konnen die Mengen auch leer sein, falls die zugehorige
Randbedingung nicht vorkommt. Damit ergibt sich folgende Problemstellung:
Lu = f, in Ω (10.3)
u = gD, auf ΓD ⊂ δΩ (10.4)
a(x) · ∂u
∂n= gN , auf ΓN ⊂ δΩ (10.5)
a(x) · ∂u
∂n− κu = gR, auf ΓR ⊂ δΩ (10.6)
Zur numerischen Losung von PDGen wird uber das Gebiet Ω ein Diskretisierungsgitter oder ei-
ne Triangulierung Ωh gelegt, aus der die Diskretisierungspunkte und Elemente entstehen, siehe auch
Abschnitt 10.1.2. Darauf aufbauend gibt es verschiedene Ansatze, um die approximative Losung der
PDG zu berechnen. Die resultierenden Gleichungssysteme konnen mittels Verfahren der numerischen
linearen Algebra gelost werden (z.B. cg-, Gauß-Seidel-, Mehrgitter-Verfahren, GMRES, ...).
Ein einfacher und intuitiver Weg, um eine PDG auf einem Gitter zu diskretisieren, ist sicherlich
die Finite Differenzen Methode (FDM). Dabei werden die in den Differentialgleichungen auftretenden
(partiellen) Ableitungen durch Differentialquotienten approximiert [Bra97]. Somit erhalt man fur jeden
Punkt des Diskretisierungsgitters einen Stern, durch den die Differentialoperatoren approximativ be-
stimmt werden. Die FDM ist jedoch nur auf strukturierte Gitter einfach anwendbar, außerdem lassen
sich inhomogene Neumannsche Randbedingungen nur sehr aufwandig realisieren. Des Weiteren stellt
die zugrunde liegende Konvergenztheorie oft zu einschrankende Anforderungen an die zu erwartenden
Losung [Bra97].
Neben der FDM gibt es noch weitere Diskretisierungsmethoden, wie etwa die Finite Volumen Metho-
de, die Randelement Methode oder die Lattice-Boltzmann-Methode. Ein sehr flexibles und weit verbrei-
tetes Verfahren zur Diskretisierung ist jedoch die Finite Elemente Methode (FEM), deren Grundlagen
und Mechanismen im Folgenden vorgestellt werden.
10.1 Die Finite Elemente Methode
Die Anwendung der FEM spaltet sich im Wesentlichen in vier Schritte auf [CSvS86]:
1. Erstellen der schwachen Form und Umwandlung in eine geeignete Bilinearform.
2. Erzeugung eines problemspezifischen Diskretisierungsgitters.
3. Wahl von geeigneten Finiten Elementen.
4. Berechnung der lokalen bzw. globalen Diskretisierungsmatrizen.
10.1.1 Die schwache Formulierung
Nach [KA00] besteht die grundsatzliche Strategie zur Gewinnung der schwachen Form einer PDG aus
den folgenden Schritten:
78
KAPITEL 10. FINITE ELEMENTE
• Erstellen der variationellen Formulierung der PDG durch Multiplikation mit Testfunktionen und
Integration uber das Problemgebiet Ω.
• Partielle Integration und Einsetzen der Randbedingungen um entsprechende Bilinearformen zu
erhalten.
• Verifikation der notigen Eigenschaften der erhaltenen schwachen Form, d.h. Elliptizitat, Stetig-
keit.
Ausgehend von der elliptischen PDG (10.2) wird die Gleichung in ihre variationelle Formulierung
umgewandelt. Es bezeichne L1loc(Ω) den Raum der messbaren Funktionen, die auf jeder kompakten
Teilmenge Ω′ von Ω integrierbar sind. Damit kann man aus dem Fundamentallemma der Variations-
rechnung [BS94] schließen, dass fur u ∈ L1loc(Ω)
∫
Ω
uϕ dµ = 0 fur ϕ ∈ C∞0 (Ω) ⇔ u = 0 fast uberall in Ω (10.7)
gilt. Darauf aufbauend kann der Begriff der Ableitung durch die wiederholte Anwendung der partiellen
Integration zur sog. schwachen Ableitung verallgemeinert werden [GR05]. Danach ist die α-te schwache
Ableitung w (bezeichnet auch mit w := Dαu) fur u ∈ C|α|(Ω) definiert durch
∫
Ω
uDαϕ dµ = (−1)|α|
∫
Ω
wϕ dµ fur ϕ ∈ C∞0 (Ω).
Mittels dieser schwachen Ableitung lassen sich die sog. Sobolev-Raume Hm,p(Ω) einfuhren, welche
die Funktionen u ∈ Lp(Ω) enthalten, die m-mal schwach differenzierbar sind und schwache Ableitungen
in Lp(Ω) besitzen [Bra97]. Im Falle p = 2 schreibt man auch kurzer Hm(Ω). Die zugehorigen Normen
berechnen sich aus der Summe der Lp-Normen der einzelnen Ableitungen.
Elliptische PDGen der Form Lu = f (Gleichung (10.2)) werden nun durch Multiplikation mit Test-
funktionen v ∈ V und Integration uber das Problemgebiet Ω in ihre schwache Formulierung uberfuhrt
∫
Ω
Lu · v dµ =
∫
Ω
f · v dµ, fur v ∈ V.
Die Aquivalenz zur starken Formulierung (10.2) wird durch Gleichung (10.7) ersichtlich. Ausgehend
von dieser schwachen Formulierung konnen durch die Anwendung der Greenschen Formel [GR05] die
quadratischen Differentialoperatoren in Form des Laplace-Operators in das Produkt der Gradienten
umgewandelt werden. Fur u ∈ H2(Ω), v ∈ H1(Ω) gilt
∫
Ω
−∇ · (a(x)∇u) · v dµ =
∫
Ω
a(x)∇u · ∇v dµ −∫
ΓN∪ΓR
a(x)∂u
∂nv dσ.
Bezeichnet L0(·, ·) den Differentialoperator L in Ω nach Anwendung der Greenschen Formel, so wird
L0 definiert durch∫
Ω
Lu · v dµ =
∫
Ω
L0(u, v) dµ −∫
ΓN∪ΓR
a(x)∂u
∂n· v dσ. (10.8)
Damit kann die schwache Form der PDG durch Einsetzen der gegebenen Neumann-Randbedingung
(a(x) ∂u∂n
= gN auf ΓN ) bzw. Robin-Randbedingung (a(x) ∂u∂n
= gR − κu auf ΓR) folgendermaßen ge-
schrieben werden∫
Ω
L0(u, v) dµ +
∫
ΓR
κu · v dσ =
∫
Ω
f · v dµ +
∫
ΓN
gN · v dσ +
∫
ΓR
gR · v dσ fur u, v ∈ V,
bzw. mit a(·, ·) : V × V → R
a(u, v) = (f, v)0,Ω + (gN , v)0,ΓN+ (gR, v)0,ΓR
fur u, v ∈ V. (10.9)
79
10.1. DIE FINITE ELEMENTE METHODE
Somit treten also die Neumann- bzw. Robin-Randbedingungen ganz naturlich in der schwachen For-
mulierung der PDG in Erscheinung. Die Dirichlet-Bedingung wird erst nach einer entsprechenden
Diskretisierung der Problemstellung in die endlich-dimensionalen Variablen eingehen.
Nach diesen einfuhrenden Betrachtungen zur Herleitung der schwachen Form einer PDG wird nun
darauf aufbauend die Finite Elemente Methode erlautert. Diese basiert auf der Wahl von geeigneten
endlich-dimensionalen Funktionenraumen, sowie der Auswahl zugehoriger Basen, durch die die auftre-
tenden Differentialoperatoren approximiert werden. Dazu benotigt man zunachst eine Triangulierung
Ωh des Problemgebietes Ω.
10.1.2 Gebietszerlegungen
Das Gebiet Ω sei in N nicht-leere, abgeschlossene Elemente TiNi=1 zerlegt, wobei das Innere eines
Elementes Ti nicht leer sei und einen Lipschitz-stetigen Rand habe. Der Schnitt des Inneren zweier
verschiedener Elemente sei leer,
Ti ∩
Tj = ∅ fur i 6= j.
Besitzt die Schnittmenge zweier Elemente Punkte, Kanten oder Flachen, so sind diese Punkte, Kanten
bzw. Flachen beider Elemente [GR05]. Daruber hinaus bezeichne
hTi:= sup
|x − y|∣
∣x, y ∈ Ti
den Durchmesser eines Elements Ti und jedes Element der Zerlegung enthalte fur ein fest vorgegebenes
κ einen Kreis mit Radius ρTimit
ρTi≥ hTi
κ.
Die so erhaltene Zerlegung muss bei krummlinig berandeten Gebieten nicht exakt mit dem Gebiet
ubereinstimmen. Beschreibt sie aber das Gebiet exakt, d.h.
Ω =
N⋃
i=1
Ti,
so wird die Zerlegung konform genannt [KA00]. Gilt fur jedes Element der Zerlegung
ρTi≥ h
κ
mit einem globalen h, so heißt die Zerlegung uniform. Weiter wird die Zerlegung TiNi=1 mit Ωh
bezeichnet, falls h der maximale Durchmesser der Elemente Ti ist.
Eine Gebietszerlegung oder auch Triangulierung muss nicht aus einheitlichen Elementen (z.B. nur
Hexaedern) bestehen, sondern kann aus unterschiedlichen Elementtypen zusammengesetzt sein. Prinzi-
piell konnen auch allgemeinere Zerlegungen definiert werden, die etwa auch hangende Knoten enthalten
(d.h. Knoten, die auf der Kante eines benachbarten Elements liegen). Dadurch entstehen im Allgemei-
nen aber Nachteile bei der Gute der Approximation. Daneben konnen die Zerlegungen auch lokal
adaptiv sein, um kritische Regionen der PDGen besser darstellen zu konnen. Aufbauend auf diesen
Triangulierungen werden nun die Finite Elemente Raume definiert.
10.1.3 Finite Elemente
Mit einer Triangulierung Ωh lassen sich nach Ciarlet [Cia02] bzgl. jedes Elementes K ∈ Ωh Finite
Elemente im Rd als Tripel (K, Vh, Σ) mit den folgenden Eigenschaften definieren (I := i|1 ≤ i ≤ N):
1. K ist eine abgeschlossene Teilmenge des Rd mit einem nicht-leeren Inneren (o
K 6= ∅) und einem
Lipschitz-stetigen Rand.
80
KAPITEL 10. FINITE ELEMENTE
2. Vh ist ein Raum reellwertiger Funktionen auf dem Gebiet K.
3. Σ beschreibt eine endliche Menge linear unabhangiger Linearformen Φi, i ∈ I, welche auf der
Menge Vh definiert sind. Die Menge Σ ist unisolvent, d.h. fur jedes αi ∈ R, i ∈ I existiert eine
eindeutige Funktion v ∈ Vh mit
Φi(v) = αi, i ∈ I.
Folglich existieren Funktionen ϕi ∈ Vh, i ∈ I, mit
Φj(ϕi) = δij , fur alle j ∈ I,
so dass alle Funktionen v ∈ Vh bzgl. der Basis ϕi|i ∈ I eine Darstellung der Art
v =∑
i∈I
αi ϕi
besitzen. Hieraus folgt, dass der Funktionenraum Vh endlich-dimensional ist mit Dimension dim Vh =
N . Mittels dieser Darstellung kann die variationelle Formulierung (10.9)
a(u, v) = (f, v)0,Ω + (gN , v)0,ΓN+ (gR, v)0,ΓR
fur v ∈ V
durch Restriktion auf den endlich-dimensionalen Raum Vh und der Vertauschung von Summation und
Integration wie folgt geschrieben werden (fur alle ϕi, ϕj ∈ Vh, j ∈ I):∑
i∈I
αi a(ϕi, ϕj) = (f, ϕj)0,Ω + (gN , ϕj)0,ΓN+ (gR, ϕj)0,ΓR
. (10.10)
Dabei werden die Basisfunktionen ϕi zur Darstellung von u Ansatzfunktionen und diejenigen fur v
Testfunktionen genannt. Weiter bezeichne ID die Menge der Indizes aus I, deren zugehorige Funktionen
ϕi zur Darstellung des Dirichlet-Randes ΓD dienen, also
ID = i ∈ I | ϕi(ΓD) ) 0 .
Definiert man damit I0 := I \ ID, so konnen in Gleichung (10.10) unter Berucksichtigung der Dirichlet-
Randbedingung u|ΓD= gD die entsprechenden Zeilen des linearen Gleichungssystems gestrichen bzw.
die feststehenden Randbedingungen auf die rechte Seite geschrieben werden [BF01]. Damit ergibt sich
fur alle j ∈ I0 und ϕi, ϕj ∈ Vh
∑
i∈I0
αi a(ϕi, ϕj) = (f, ϕj)0,Ω + (gN , ϕj)0,ΓN+ (gR, ϕj)0,ΓR
− a(ϕj , gD). (10.11)
Hierbei werden Ansatz- und Testfunktionen aus dem gleichen Funktionenraum Vh gewahlt, im Allge-
meinen jedoch konnen diese auch aus verschiedenen Raumen stammen, siehe Abschnitt 10.2.1. Daruber
hinaus kann der Finite Elemente Ansatz auch auf komplexe oder vektorwertige Ansatz- bzw. Testfunk-
tionen erweitert werden.
10.1.4 Referenzelemente und Transformationen
Zum Aufstellen einer globalen Diskretisierungsmatrix werden nach Gleichung (10.11) die lokalen Stei-
figkeitsmatrizen a(ϕi, ϕj) benotigt, die sich als Summe der elementweisen Diskretisierungssterne be-
rechnen. Es gilt
a(ϕi, ϕj) =∑
K∈Ωh
a(ϕi, ϕj)∣
∣
Kfur i, j ∈ I,
wobei die Summe nur uber die Elemente K von Ωh zu berechnen ist, auf denen ϕi und ϕj einen ge-
meinsamen Trager besitzen. Haben die Funktionen keinen gemeinsamen Trager, so ergibt das zugrunde
liegende Integral immer null. Folglich gilt mit Sij = supp(ϕi) ∩ supp(ϕj)
a(ϕi, ϕj) =∑
K∈Sij
a(ϕi, ϕj)∣
∣
Kfur i, j ∈ I.
81
10.1. DIE FINITE ELEMENTE METHODE
Die Integrale auf einem Element K sind im Allgemeinen mittels direkter Integration nur sehr aufwandig
berechenbar. Nur bei strukturierten Triangulierungen konnen die Integrationsgrenzen ohne zusatzliche
Fallunterscheidungen bestimmt werden. Fur beliebige Elemente dagegen ergeben sich dadurch sehr
ineffiziente Algorithmen. Aus diesem Grund wird die Integration mittels einer Transformation auf
einem Referenzelement berechnet. Dazu bezeichne TK eine bijektive Abbildung des Referenzelementes
E auf das Element K,
TK : E → K.
Eine solche Transformation hangt von der Art und der Lage des Elements K ab und bildet die Eck-
punkte des Referenzelementes auf die entsprechenden Ecken von K ab. Besitzt TK im Speziellen die
Struktur TK(x) = PK0 +FK(x) mit einer linearen Abbildung FK , so handelt es sich bei der Menge der
damit erzeugten Elemente um affine Familien.
Abbildung 10.1: Transformation des Referenzelements auf allgemeine Dreiecke.
Die Funktionen auf einem allgemeinen Element K lassen sich uber die Transformation aus den
zugehorigen Funktionen auf dem Referenzelement bestimmen,
f : K → R und f : E → R, mit f = f T−1K bzw. f = f TK .
Durch die Transformation werden somit Funktionen auf K durch Funktionen auf E dargestellt. Die
transformierten Ableitungen mussen zusatzlich durch eine entsprechende Multiplikation mit der Jacobi-
Matrix DTKvon TK bzw. DT−1
Kvon T−1
K angepasst werden. Dies kann einfach durch die Anwendung
der Kettenregel nachgewiesen werden,
∇f = ∇(
f T−1
K
)
=(
∇f T−1
K
)
·DT−1
K.
bzw.
∇f = ∇ (f TK) = (∇f TK) · DTK
Gleiches gilt auch fur die Beziehung zwischen den Richtungsvektoren a und a der jeweiligen Koordi-
natensysteme von E bzw. K:
a = a ·DT−1
Kbzw. a = a ·DTK
Prinzipiell ist dabei zu beachten, dass die Ableitung DT−1
Kvon T−1
K aus der zugehorigen Jacobi-
Matrix von TK berechnet werden kann. Das bedeutet, dass die die Umkehrabbildung T−1
k nicht be-
stimmt werden muss, was auch nicht immer moglich ist. Da die Abbildung TK nach Voraussetzung auf
E bijektiv ist, kann nach dem Umkehrsatz [Heu98] gefolgert werden:
DT−1
K(x) = D−1
TK
(
T−1
K (x))
.
Diese Beziehungen werden nun bei der Transformation der Integranden im Koordinatensystem K
auf die entsprechenden Integranden bzgl. E angewandt. Danach ergibt die Substitutionsregel [Heu98]
82
KAPITEL 10. FINITE ELEMENTE
Abbildung 10.2: Abbildung von Richtungen im Referenzelement E in das allgemeine Element K.
durch Einsetzen von x := TK(x) und der entsprechenden Multiplikation mit der Determinanten der
Jacobi-Matrix ∫
K
g(x) dµK =
∫
E
G(TK(x)) det (DTK(x)) dµE ,
wobei g den ursprunglichen Integranden auf K und G den transformierten Integranden ebenfalls auf K
bezeichne. Ahnliches gilt auch fur die Berechnung von Rand- bzw. Oberflachenintegralen. Ist S ⊂ R3
eine Oberflache und ΦS : EB ⊂ R2 → S eine Parametrisierung der Flache EB uber dem zweidimensio-
nalen Referenzelement, so berechnet sich das Oberflachenintegral durch:∫
S
g(x) dσ =
∫
EB
G(ΦS(x))
∣
∣
∣
∣
∂
∂x1
ΦS(x) × ∂
∂x2
ΦS(x)
∣
∣
∣
∣
dµEB.
Außerdem gilt im Falle eines Randes S ⊂ R2 mit der Parametrisierung ΦS : [0, 1] → S uber dem
Einheitsintervall die Beziehung∫
S
g(x) dσ =
∫ 1
0
G(ΦS(x))∣
∣
∣
.
ΦS(x)∣
∣
∣ dx
Durch diese Substitutionen konnen die Integrale mittels der Funktionen auf den entsprechenden
Referenzelementen berechnet werden, z.B. fur die schwache Formulierung des Laplace-Operators:∫
K
∇ϕi(x) · ∇ϕj(x) dµK =
=
∫
K
∇ϕi
(
T−1
K (x))
· D−1
TK(T−1
K (x)) · D−TTK
(T−1
K (x)) · ∇ϕj
(
T−1
K (x))
dµK
=
∫
E
∇ϕi(x) · D−1
TK(x) · D−T
TK(x) · ∇ϕj(x) det (DTK
(x)) dµE .
Die vorher angesprochenen Transformationen der affinen Familien ergeben sich nur in besonderen
Fallen, wie beispielsweise bei Dreiecken, Parallelogrammen, Tetraedern oder Parallelepipeden. Fur
allgemeinere Geometrien oder auch fur isoparametrische Ansatze ergeben sich keine affin-linearen For-
meln. Bei isoparametrischen Elementen werden an den Seiten (bzw. Flachen) zusatzliche Transforma-
tionspunkte definiert, mittels derer die Abbildung krummlinige Rander besser approximieren kann.
Abbildung 10.3 zeigt die Transformation eines isoparametrischen Dreiecks-Elements mit einer krumm-
linigen Kante, generell konnen aber auch mehrere krummlinige Kanten vorkommen.
Neben der Lage des Elements K hangt die Transformationsformel noch von der Nummerierung
der abgebildeten Punkte ab. Betrachtet man beispielsweise die Transformation von isoparametrischen
Dreieckselementen wie in Abbildung 10.3, so lautet die zugehorige Formel [GR05]:
TK(x, y) = PK0 +
(
PK1 − PK
0
)
x +(
PK3 − PK
0
)
y +(
4PK2 − 2
(
PK1 + PK
3
))
xy.
Bei Verwendung einer anderen Nummerierung der Punkte in den Elementen, sind der Transformati-
onsformel die entsprechenden Punkte zu vertauschen.
Mit den prasentierten Mechanismen konnen die Integrale auf einem allgemeinen Element K aus den
Basisfunktionen auf dem Referenzelement sowie der Abbildung TK und deren Ableitungen berech-
net werden. Zur einfachen und effektiven Durchfuhrung der Integration uber den Referenzelementen
verwendet man numerische Quadraturverfahren.
83
10.2. EINIGE FINITE ELEMENTE APPROXIMATIONEN
Abbildung 10.3: Transformation des Referenzelement E auf ein isoparametrisches Dreieck mit einer
krummlinigen Kante.
10.1.5 Numerische Quadratur
Die analytische Berechnung der transformierten Integrale uber dem Referenzelement gestaltet sich ins-
besondere fur nicht-affinlineare Transformationen als schwierig. Denn diese besitzen keine konstanten
Ableitungen, so dass die Jacobi-Determinante ebenfalls nicht konstant ist. In der Ableitung der Umkehr-
abbildung konnen dabei gebrochen-rationale Funktionen entstehen. Aber auch bei gebrochen-rationalen
oder trigonometrischen Basisfunktionen konnen die zugehorigen Stammfunktionen nur aufwandig oder
uberhaupt nicht bestimmt werden.
Aus diesen Grunden verwendet man numerische Quadraturverfahren, mittels derer eine approxi-
mative Berechnung der Integrale durch Auswertung der Integranden an bestimmten Stutzstellen mit
entsprechender Gewichtung moglich ist. Eine besonders effiziente Methode hierfur ist das Gauß’sche
Quadraturverfahren [SB02], da es mit einer geringen Anzahl an Auswertungen der Integranden aus-
kommt. Allgemein ist
∫
S
f(x) dµ ≈q
∑
j=1
χjf(ξj),
wobei χj das zu der Stutzstelle ξj gehorige Gewicht bezeichnet. Dazu werden zu einer Gewichtsfunktion
n orthogonale Polynome benotigt. Die Nullstellen des n-ten Polynoms bilden dann genau die Stutz-
stellen des entsprechenden Quadraturverfahrens vom Grad n. Diese Quadraturverfahren berechnen die
Integrale fur Polynome vom Grad 2n − 1 exakt. Dies ermoglicht also mit wenigen Auswertungen des
Integranden eine exakte Integration von Polynomen von kleinen Grad, und liefert auch fur andere
Funktionen gute Approximationen.
Fur Quadrate bzw. Wurfel konnen die Gauß-Punkte und Gewichte uber die Tensorprodukte der
eindimensionalen Stutzstellen und Gewichte berechnet werden. Dagegen werden fur Dreiecke oder
Tetraeder eigens berechnete Gauß-Punkte benotigt, da hier angepasste orthogonale Polynome zu ver-
wenden sind. Die entsprechenden Werte und Gewichte konnen aus der Literatur entnommen werden,
siehe z.B. [SB02, JL01].
10.2 Einige Finite Elemente Approximationen
Wie bereits erwahnt basieren die verschiedenen Finiten Elemente Ansatze auf der Wahl endlich-
dimensionaler Funktionenraume Vh. Im Folgenden werden einige der gangigen Kriterien zur Wahl
dieser Funktionenraume zusammengefasst.
84
KAPITEL 10. FINITE ELEMENTE
10.2.1 Konforme Finite Elemente
Wird der endlich-dimensionale Raum der Basisfunktionen als Teilmenge von V gewahlt, so ubertragen
sich die darin gultigen Eigenschaften auf den kleineren Ansatzraum Vh ⊂ V [GR05]. Dies gilt damit
auch fur die Bilinearformen der schwachen Formulierung, wenn die zugrunde liegenden Integrale exakt
berechnet werden.
Im Falle einer V -elliptischen Bilinearform a(·, ·) existiert nach dem Lemma von Lax-Milgram [BS94]
fur jedes f eine eindeutige Losung uh. Falls Hm0 (Ω) ⊂ V ⊂ Hm(Ω) ist, gilt fur den Approximationsfehler
nach dem Lemma von Cea [Bra97] die folgende Abschatzung:
||u − uh|| ≤M
γinf
vh∈Vh
||u − vh||.
Entsprechende Schranken fur den Interpolationsfehler ergeben sich beispielsweise aus dem Bramble-
Hilbert-Lemma [Cia02]. Falls die Raume Vh ⊂ V asymptotisch dicht in V gewahlt werden, kann jede
Funktion v ∈ V beliebig genau durch Funktionen uh ∈ Vh approximiert werden [GR05].
Diese Ansatze werden fur symmetrische Bilinearformen auch als Ritz-Verfahren bezeichnet, wobei
uh als Losung des Minimierungsproblems in Vh angesehen wird. Im allgemeinen Fall spricht man von
Galerkin-Verfahren oder konformen FEM und betrachtet die endlich-dimensionale Variationsgleichung
(10.10) als direkte Diskretisierung der ursprunglichen schwachen Formulierung (10.9). Werden dabei
die Ansatzfunktionen u ∈ Vh und die Testfunktionen v ∈ Wh aus verschiedenen, N -dimensionalen
Raumen Vh, Wh ⊂ V gewahlt, so spricht man auch von Petrov-Galerkin-Verfahren [ZT00a].
Das bekannteste Beispiel konformer Finiter Elemente fur stetige Bilinearformen sind lineare Ansatz-
funktionen. Diese sind an einer Ecke des Elements eins und verschwinden allen anderen Ecken.
10.2.2 Nichtkonforme Finite Elemente
Bei gewissen Problemstellungen ist es sinnvoll, die Ansatzfunktionen und Testfunktionen aus einem
Raum zu wahlen, der nicht Teilmenge von V ist, also Vh 6⊂ V . Daneben kann es notig sein, die
Bilinearform a(·, ·) durch ah(·, ·) zu approximieren. Eine solche nichtkonforme Diskretisierung ist unter
anderem in den folgenden Fallen sinnvoll [GR05]:
• wenn die Wahl eines Funktionenraumes Vh ⊂ V zu aufwandig ist (z.B. bei PDGen hoherer
Ordnung),
• wenn die Bilinearformen bzw. die Integrale nur naherungsweise berechnet werden konnen,
• wenn inhomogene Randbedingungen oder krummlinige Rander von Ω keine exakte Darstellung
im diskretisierten Problem erlauben.
Die Konvergenz dieser nichtkonformen Finiten Elemente wird durch die Lemmas von Strang geliefert,
welche in der Abschatzungen des Fehlers zusatzlich die approximierende Bilinearform ah(·, ·) bzw. den
Konsistenzfehler berucksichtigen [GR05]. Ein sehr bekanntes Beispiel nicht-konformer Finiter Elemente
in 2D sind die Crouzeix-Raviart-Elemente [Bra97]. Diese Formfunktionen sind auf jedem Element linear
und besitzen nur Stetigkeit in den Seitenmittelpunkten. Die dadurch zusammengesetzten Funktionen
vh ∈ Vh sind im Allgemeinen nicht stetig auf Ω.
10.2.3 Gemischte FEM
Eine weitere Kategorie der Finiten Elemente Ansatze bilden die Diskretisierungen von partiellen Dif-
ferentialgleichungen unter Nebenbedingungen [BS94]. Diese Nebenbedingungen konnen etwa aus den
Dirichlet-Randbedingungen hervorgehen oder beispielsweise aus der zusatzlichen Inkompressibilitats-
bedingung des Stokes-Problem entstehen. Prinzipiell sucht man hierbei die Losung (u, p) ∈ V × W
85
10.3. FE-SOFTWARE
vona(u, v) + b(v, p) = (f, v) fur alle v ∈ V,
b(u, w) = (g, w) fur alle w ∈ W,(10.12)
wobei a(·, ·) : V × V → R und b(·, ·) : V × W → R gegebene stetige Bilinearformen bezeichnen.
Die Funktionenraume Vh und Wh werden im Allgemeinen nicht identisch gewahlt, da sonst in vielen
Fallen die Nebenbedingung zu stark in die Diskretisierung einfließt [Bra97, ZT00b]. Dabei entstehen
je nach Wahl der Raume Vh bzw. Wh konforme oder nichtkonforme Finite Elemente Ansatze. Zur
generellen Konvergenz der Verfahren spielt die Babuska-Brezzi-Bedingung eine wichtige Rolle, die eine
abgestimmte Wahl der Funktionenraume Vh und Wh fordert [GR05].
Bekanntes Beispiel ist hierbei etwa das Taylor-Hood-Element [BS94] zur Diskretisierung der Stokes-
Gleichungen. Dabei werden fur die Geschwindigkeiten quadratische Polynome, fur den Druck jedoch
nur lineare Polynome gewahlt.
10.2.4 Vektorwertige Finite Elemente
Eine letzte Klasse Finiter Elemente, die hier erwahnt werden soll, bilden die vektorwertigen Finiten Ele-
mente. Die Ansatz- bzw. Testfunktionen mussen nicht zwingend Skalarfelder sein, sondern konnen auch
– wenn der Problemstellung entsprechend – vektorwertige Funktionen sein. Dies ist z.B. bei den Stokes-
Gleichungen der Fall, oder bei der Behandlung der vektoriellen Helmholtzgleichung. Dabei werden die
Ansatz- bzw. Testfunktionenraume oft mit Zusatzbedingungen gewahlt, wodurch die Funktionen be-
stimmte Nebenbedingungen per Definition erfullen. Werden auf diese Raume konforme FE-Ansatze
angewandt, kann die Nebenbedingung bei der Diskretisierung weggelassen werden [Mon03, GR05].
Doch die Gestaltung solcher konformer FEM-Diskretisierungen, also die Wahl endlich-dimensionaler
Funktionenraume, welche die Nebenbedingung erhalten, erweist sich als nicht-trivial [Mon03]. Dazu
werden spezielle Finite Elemente benotigt, welche beispielsweise durch eingeschrankte Flussrichtungen
bzgl. der zugrunde liegenden Elementgeometrien die Eigenschaften der Ausgangsraume erhalten [Jin02,
Mon03]. Bekannt sind hier vor allem die Nedelec-Elemente oder auch die Elemente von Raviart und
Thomas. Dazu zeigt die Abbildung 10.4 die Nedelec-Kanten-Elemente fur einen divergenz-konformen
FE-Ansatz. Die Pfeile auf den Kanten deuten die einzelnen Basisfunktionen an, die nur entlang einer
Kante eine Tangential-Komponente besitzen. Entsprechende Elemente lassen sich auch fur Vierecke
bzw. Hexaeder konstruieren.
Abbildung 10.4: Kantenelemente in 2D und 3D. Die drei bzw. sechs Basisfunktionen besitzen nur
eine Tangential-Komponente entlang einer Kante, was durch die Pfeile dargestellt wird.
10.3 FE-Software
Wie aus diesen teilweise sehr aufwandigen Ansatzen deutlich wird, benotigt eine flexible, computer-
gestutzte Realisierung Finiter Elemente Diskretisierungen effiziente Implementierungen der zugrunde
86
KAPITEL 10. FINITE ELEMENTE
liegenden mathematischen Berechnungen. Da die Finite Elemente Methode auch in der industriel-
len Simulation immer mehr Anwendung findet, existieren eine Reihe kommerzieller Software-Pakete,
beispielsweise ANSYS, ABAQUS oder Diffpack [Lan02].
Daneben wurden eine Reihe frei verfugbarer Software-Bibliotheken entwickelt, wie etwa ExPDE
[Pfl01], Overture [Hen02, BCHQ97] oder ALBERTA [SS04]. Diese Programmpakete verknupfen die FE-
Implementierungen mit einer Reihe von typischen Datenstrukturen bzw. Diskretisierungsgittern. Dazu
werden auch entsprechende Loser oder Schnittstellen angeboten, um auf die diskretisierten Gleichungen
entsprechende Losungsverfahren anwenden zu konnen.
Doch gerade in der Forschung, wie etwa bei der Untersuchung effizienter (paralleler) Datenstruk-
turen oder der Suche nach problemspezifischen Approximationsansatzen, werden oft Finite Elemente
Diskretisierungen ohne zugrunde liegende Datenstrukturen benotigt.
87
10.3. FE-SOFTWARE
88
KAPITEL 11. COLSAMM
11 Colsamm - Effiziente Berechnung von
FE-Matrizen
Die numerische Losung von partiellen Differentialgleichungen mittels der Finiten Elemente Metho-
de teilt sich nach dem vorherigen Kapitel in mehrere Schritte auf. Der FEM-spezifische Diskreti-
sierungsschritt besteht dabei im Berechnen und Assemblieren der globalen bzw. lokalen Diskretisie-
rungsmatrizen. Dabei birgt dieses Aufstellen der Diskretisierungsmatrizen vor allem fur Anwender
aus nicht-mathematischen Gebieten einige technische Realisierungshurden. In einfachen Fallen, al-
so bei strukturierten Gittern und positionsunabhangigen Diskretisierungssternen, ist das Aufstellen
der Matrizen ohne Transformationen oder aufwandige Integrationen moglich. Doch bei ortsabhangi-
gen Operatoren, unstrukturierten Gittern oder hohergradigen Basisfunktionen sind die Integrale der
schwachen Formulierung uber eine Transformation auf ein Referenzelement und einer numerischen
Integration zu bestimmen (Abschnitte 10.1.4 und 10.1.5). Die dazu erforderlichen Berechnungen fur
die transformierten Differentialoperatoren erschweren die Implementierung von solchen allgemeineren
FEM-Diskretisierungen zusatzlich. Besonders dann, wenn die Anwender eigentlich an der praktischen
Verwendung der FEM interessiert sind und nicht an der Programmierung der zugrunde liegenden ma-
thematischen Details. Dabei entstehen oft sehr problembezogene und unflexible Implementierungen, die
nur schwer verallgemeinert und portiert werden konnen. Daruber hinaus benotigen die mathematischen
Methoden eine leistungsstarke Realisierung, da diese beim Aufstellen der Diskretisierungsmatrizen sehr
intensiv benutzt werden.
11.1 Grundidee und Programmstruktur
Ziel bei der Entwicklung von Colsamm war eine effiziente, flexible und benutzerfreundliche Biblio-
thek zur Berechnung Finiter Elemente Diskretisierungen. Dabei sollte ein kompaktes Programmpa-
ket entstehen, das mit minimalen Schnittstellen und festem Funktionsumfang moglichst intuitiv in
C++-Programmen fur numerische FE-Approximationen genutzt werden kann. Insbesondere sollten
keine Einschrankungen auf die Art oder Implementierung der Diskretisierungsgitter und eingesetz-
ter Losungsverfahren entstehen. Die Bibliothek arbeitet deshalb ohne jegliche Informationen uber die
Verknupfungen im zugrunde liegenden Diskretisierungsgitter. Aus diesem Grund konnen innerhalb
Colsamm die berechneten lokalen Steifigkeitsmatrizen nicht zu den globalen Diskretisierungsmatri-
zen assembliert werden. Diese sind vom Anwender selbst zusammenzustellen. Abbildung 11.1 stellt
strukturell die numerische Losung partieller Differentialgleichungen dar und zeigt die Verwendung von
Colsamm zum Berechnen der lokalen Diskretisierungsmatrizen durch die Iteration uber die Elemente
des Gitters.
11.1.1 Programmablauf
Die Benutzung von Colsamm ist neben der Benutzerfreundlichkeit vor allem auch auf hohe Effizienz
ausgerichtet. Aus diesem Grund ist der Programmablauf so strukturiert, dass moglichst keine unnotigen
Berechnungen durchgefuhrt werden mussen. Dazu ist die Verwendung von Colsamm in drei Einheiten
aufgebaut.
89
11.1. GRUNDIDEE UND PROGRAMMSTRUKTUR
Abbildung 11.1: Strukturelle Darstellung der numerischen Losung von PDGen mit der Finiten Ele-
mente Methode unter Verwendung von Colsamm.
Die zum Erstellen der lokalen Steifigkeitsmatrizen benotigten Integrale werden mittels Gaußqua-
dratur uber dem Referenzelement berechnet. Diese Integrale bestehen aus den Kombinationen der
Basisfunktionen auf dem Referenzelement und der Transformation fur das aktuelle Element. Die Ba-
sisfunktionen werden beim Anlegen eines Finiten Elements gesetzt. Dabei werden die Basisfunktionen
und deren Ableitungen an den Gaußpunkten auf dem Referenzelement ausgewertet, und diese Daten
werden abgespeichert. Dabei handelt es sich um kleine Datenmengen, denn pro Basisfunktion sind
dabei hochstens vier Werte fur jeden Gaußpunkt zu speichern. Diese Werte mussen fur ein Finites
Element nur einmal bestimmt werden.
Nach dieser Initialisierung werden in der Iteration uber die Elemente des Diskretisierungsgitters die
Koordinaten des aktuellen Elements gesetzt. Diese Daten werden verwendet, um die Transformation
und die zugehorige Jacobi-Matrix ebenfalls an den Gaußpunkten zu berechnen und abzuspeichern.
Zusatzlich wird aus den Ableitungen die Jacobi-Determinante berechnet. Insgesamt handelt es sich
dabei ebenfalls um geringe Datenmengen, jedoch mussen diese Berechnungen fur jedes Element neu
durchgefuhrt werden. Sind auf einem Element aber mehrere Integrationen durchzufuhren, so konnen
die gespeicherten Daten wieder verwendet werden.
Zuletzt werden bei der numerischen Quadratur der schwachen Formulierung die gespeicherten Werte
der Funktionen an den Gaußpunkten entsprechend dem Integranden kombiniert und daraus das Integral
berechnet. Diese Integrale sind fur alle Kombinationen von Basisfunktionen zu bestimmen. Abbildung
11.2 zeigt anschaulich diesen Programmablauf zur Berechnung der lokalen Diskretisierungsmatrizen
mittels Colsamm.
Fur die entsprechende Festlegung der Schnittstellen und des Funktionsumfangs wurde die bereits
in Abschnitt 10.1.3 aufgefuhrte klassische Definition von Ciarlet [Cia02] herangezogen. Diese mathe-
matische Definition wird etwas anwendungsorientierter formuliert. Danach hat ein Finites Element
(K, P, Σ) die folgenden Eigenschaften:
• K ⊂ Rn ist ein Element mit nicht-leerem Inneren und einem Lipschitz-stetigem Rand. In den
90
KAPITEL 11. COLSAMM
Abbildung 11.2: Programmablauf von Colsamm zur Berechnung der lokalen Diskretisierungsmatrizen.
Anwendungen sind die Elemente K meist Polyeder im Rn, oder krummlinig berandete Polyeder
(isoparametrische Elemente).
• P ist ein Funktionenraum, der durch eine Menge von Basisfunktionen definiert ist. In vielen
Anwendungen sind dies reellwertige Polynome, es konnen jedoch auch trigonometrische oder
komplexwertige Funktionen eingesetzt werden.
• Die Freiheitsgrade (bei Ciarlet erklart durch lineare Funktionale) lassen sich mittels Basisfunk-
tionen auf einem Referenzelement definieren. Von diesem ausgehend konnen alle erforderlichen
Funktionen auf den allgemeinen Elementen durch entsprechende transfromationsabhangige Um-
formungen bestimmt werden.
Aus einer gegebenen partiellen Differentialgleichung, bzw. aus dem zugrunde liegenden Diskretisie-
rungsgitter, ergeben sich noch weitere, fur die einzelnen Finiten Elemente wichtige, Informationen.
Dies sind
• die Dimension des Finiten Elements,
• die Struktur bzw. Geometrie des Referenzelements,
• die Anzahl der Ecken des Finiten Elements die zur Bestimmung der Transformation benotigt
werden, und
• die Unterscheidung zwischen inneren Elementen bzw. Randelementen.
Da diese problemspezifischen Parameter der Finiten Elemente zum Teil unterschiedliche Berech-
nungsmechanismen zur Folge haben, wurde Colsamm als Template-Bibliothek konstruiert. Genauere
Erlauterungen zur zugrunde liegenden Programmstruktur, sowie zu wichtigen Implementierungsdetails
von Colsamm sind im Anhang B zusammengestellt.
In den folgenden Abschnitten werden die Schnittstellen und deren Verwendung genauer betrachtet
und diskutiert. Zunachst wird ein einfaches Beispiel der grundsatzlichen Anwendung von Colsamm zur
Erstellung der Diskretisierungsmatrizen prasentiert.
11.2 Assemblierung von Diskretisierungsmatrizen
Ausgehend von der schwachen Formulierung (10.11) werden zur Berechnung der globalen Diskretisie-
rungsmatrizen die lokalen Sterne benotigt. Diese berechnen sich aus den jeweiligen Bilinearformen bzw.
91
11.2. ASSEMBLIERUNG VON DISKRETISIERUNGSMATRIZEN
den zugrunde liegenden Integralen auf den einzelnen Elementen. Da diese Integrale nur dann von Null
verschieden sind, wenn die Basisfunktionen ϕi und ϕj einen gemeinsamen Trager besitzen, kann die In-
tegration auf die Summe uber die im gemeinsamen Trager befindlichen Elemente eingeschrankt werden.
Also folgt z.B. fur L0(·, ·), dem Integranden aus Gleichung (10.8), mit Sij = supp(ϕi) ∩ supp(ϕj)
∫
Ω
L0(ϕi, ϕj) dµ =∑
K∈Sij
∫
K
L0(ϕi, ϕj) dµ.
Analoges gilt fur die Berechnung der weiteren Integrale bzw. der Randintegrale. Wahrend diese lokalen
Sterne mittels Colsamm berechnet werden konnen, sind die globalen Diskretisierungsmatrizen durch
den Anwender selbst zusammenzusetzen. Je nach Trager der Basisfunktionen, die von der Art, Lage und
Nummerierung der Freiheitsgrade sowie vom Diskretisierungsgitter selbst abhangen, sind die Werte der
lokalen Steifigkeitsmatrizen auf die entsprechenden Positionen in den globalen Matrizen zu addieren.
Mittels Colsamm werden also die elementweisen Integrale der Form
∫
K
L0(ϕi, ϕj) dµ,
berechnet, wobei K die Elemente des Gitters durchlauft. Diese Integrale sind nur fur solche Elemente
zu berechnen, auf denen der gemeinsame Trager der aktuellen Basisfunktionen ϕi bzw. ϕj nicht leer
ist. Die Schnittstelle von Colsamm baut dabei auf der schwachen Formulierung einer PDG auf, um
eine besonders benutzerfreundliche und intuitive Anwendung zu ermoglichen.
Als einfuhrendes Beispiel dient die schwachen Formulierung des Poisson-Problems in 3D auf einem
Tetraedergitter mit der variationellen Form des Laplace-Operators
∫
K
∇ϕi · ∇ϕj dµ,
wobei K ein beliebiger Tetraeder des Diskretisierungsgitters sei. Weiter werden die Koordinaten des
i-ten Eckpunktes des Tetraeders mit PKi = (PK
i,x, PKi,y, PK
i,z), i ∈ 0, 1, 2, 3 bezeichnet. Ein Tetraeder-
Element mit linearen Basisfunktionen wird durch
ELEMENTS : : Tet rahedron K;
initialisiert. Weiter seien die Koordinaten der vier Ecken sequentiell in einem Vektor der Lange zwolf
gespeichert. Zusatzlich werde eine zweidimensionale Datenstruktur mittels STL Vektoren fur die lokalen
Diskretisierungssterne initialisiert.
s t d : : v ec to r <double> c o o r d i n a t e s ( 1 2 ) ;
// x0 , y0 , z0 , x1 , y1 , z1 , x2 , y2 , z2 , x3 , y3 , z3
s t d : : v ec to r <s t d : : v ec to r <double> > s t e n c i l ;
Die Liste der Koordinaten des aktuellen Elements wird uber den Klammeroperator (operator()) an das
Finite Element K ubergeben, wodurch die Transformation und die damit zusammenhangenden Werte
berechnet werden konnen. Danach kann die Integration fur die schwache Formulierung des Laplace-
Operators aufgerufen werden:
s t e n c i l = K( c o o r d i n t e s ) . i n t e g r a t e ( grad ( v ( ) ) ∗ grad (w ( ) ) ) ;
Damit berechnet Colsamm fur alle 16 moglichen Kombinationen der vier linearen Basisfunktionen die
numerische Integration uber das Finite Element K mit den Eckpunkten PK0 , PK
1 , PK2 , PK
3 .
Die Werte dieses Diskretisierungssterns in der lokalen Nummerierung (0 bis 3) mussen an die ent-
sprechenden Positionen der globalen Matrix addiert werden. Dazu sind die Freiheitsgrade in der lokalen
Nummerierung mit den zugehorigen Freiheitsgraden in der globalen Nummerierung zu identifizieren.
Den Gesamtablauf der FEM-Diskretisierung stellt Listing 11.1 dar. Darin werden nochmals die ein-
zelnen Schritte zur Assemblierung der Diskretisierungsmatrizen mittels Colsamm aufgefuhrt, wobei
92
KAPITEL 11. COLSAMM
FiniteElement elementList die Liste der Finiten Elemente beschreibt, die aus der Diskretisierung des Ge-
biets entstehen. Diese Objekte besitzen die Methode CoordinatesAndNumbering, welche die Koordinaten
des Elements und die globale Nummerierung der Freiheitsgrade auf diesem Element setzt.
Listing 11.1: Beispiel-Programm zur Anwendung von Colsamm
/∗ 1 . E r s t e l l u n g des D i s k r e t i s i e r u n g s g i t t e r s ∗/
F i n i t eE l emen t e l emen t L i s t ;
/∗ 2 . I n i t i a l i s i e r u n g de r g l o b a l e n D i s k r e t i s i e r u n g s m a t r i x ∗/
Spa r seMatr i x A ;
/∗ 3 . Anlegen des Te t r a ede r e l emen t s ∗/
ELEMENTS : : Tet rahedron K;
/∗ 4 . I n i t i a l i s i e r u n g de r H i l f s v e k t o r e n ∗/
s t d : : v ec to r <i n t > g l o b a l ( 4 ) ;
s t d : : v ec to r <double> c o o r d i n a t e s ( 1 2 ) ;
s t d : : v ec to r <s t d : : v ec to r <double> > s t e n c i l ;
/∗ 5 . I t e r a t i o n uebe r d i e Elemente ∗/
f o r ( i n t i =0; i < numberElements ; ++i )
/∗ 5 . 1 . Bestimmung der Koord ina ten und g l o b a l e n Nummerierung ∗/
e l emen t L i s t [ i ] . Coord inatesAndNumber ing ( coo r d i na t e s , g l o b a l ) ;
/∗ 5 . 2 . Se tz en de r Koord ina ten des a k t u e l l e n Elements ∗/
K( c o o r d i n a t e s ) ;
/∗ 5 . 3 . Berechnen der l o k a l e n D i s k r e t i s i e r u n g s m a t r i x ∗/
s t e n c i l = K. i n t e g r a t e ( grad ( v ( ) ) ∗ grad (w ( ) ) ) ;
/∗ 5 . 4 . Add i t i on zur g l o b a l e n D i s k r e t i s i e r u n g s m a t r i x ∗/
f o r ( i n t j =0; j < K. s i z e S e t 1 ( ) ; ++j )
f o r ( i n t k=0; k < K. s i z e S e t 1 ( ) ; ++k )
A[ g l o b a l [ j ] ] [ g l o b a l [ k ] ] += s t e n c i l [ j ] [ k ] ;
/∗ 6 . Loesung der numer i schen G l e i chungen ∗/
. . .
11.3 Integranden
Die Integranden der schwachen Formulierungen, die mittels Colsamm implementiert werden konnen,
bestehen aus den Kombinationen von Basisfunktionen mit Differentialoperatoren sowie problemspezifi-
schen Parametern und Funktionen. Diese konnen mit den naturlichen Rechenoperationen in sinnvoller
Art und Weise verknupft werden. Dafur verwendet Colsamm eine ET-Implementierung zur Speicherung
von Ausdrucken aus Kapitel 5.4. Diese Variante ermoglicht eine separate Deklaration und Kapselung
von Integrationstermen außerhalb des eigentlichen Integrationsaufrufs. Somit konnen auch komplexe
Integranden ubersichtlich programmiert werden, die sich sonst uber mehrere Zeilen erstrecken wurden.
Die Speicherung solcher Integrandenausdrucke in Variablen wird mittels des Makros Define Integrand (.)
durchgefuhrt (siehe auch Abschnitt 7.3). Dabei wird als erster Parameter die Integrandenformel gesetzt
und der Name des Ausdrucks als zweites. Damit kann beispielsweise der Gradient von zweidimensio-
nalen Basisfunktionen folgendermaßen gespeichert werden:
De f i n e I n t e g r a n d ( grad2D ( v ( ) ) , g rad v ) ;
Die so definierten Variablen sind wie gewohnt in den Integranden zu verwenden.
Neben der ET-Variante zur Abspeicherung der Integranden existiert auch eine FET-Implementie-
rung, die mit dem Compiler-Flag −DFET COLSAMM ausgewahlt werden kann. Dabei sind jedoch, wie
in Kapitel 6.2 beschrieben, alle ausdrucksabhangigen Werte in nummerierte Klassen zu speichern, was
die Anwendung weniger benutzerfreundlich macht. Andererseits kann dadurch bei manchen Compilern
eine performantere Berechnung der lokalen Steifigkeitsmatrizen erzielt werden. Im Folgenden werden
nun die moglichen Bestandteile der Integranden zusammengefasst, jedoch nur fur Anwendungen der
ersten ET-Variante.
93
11.3. INTEGRANDEN
11.3.1 Platzhalter fur Basisfunktionen
Wie bereits im vorherigen Beispiel zu sehen, werden mit v () und w () die ϕi und ϕj aus der schwa-
chen Formulierung bezeichnet. Diese stehen fur Platzhalter der Funktionen aus der Menge der Ansatz-
und Testfunktionen. Intern werden dafur die jeweiligen vorberechneten Werte der Funktionen einge-
setzt und somit durch entsprechende Kombinationen mit der Transformation die Diskretisierungsster-
ne berechnet. Weiter kann eine zweite Menge von Basisfunktionen definiert werden, die in gemischten
FE-Ansatzen verwendet werden kann (Abschnitt 10.2.3). Dabei beschreibt p () einen Platzhalter fur
Funktionen aus dieser zweiten Menge der Basisfunktionen.
Aus dem Vorkommen dieser Platzhalter in den Integranden bestimmt sich die Dimension der re-
sultierenden Diskretisierungssterne. Die Große eines Sterns in den einzelnen Richtungen erfolgt nach
folgender Reihenfolge der Platzhalter: v → w → p. Der Anwender kann mit den Memberfunktionen
size Set1 () bzw. size Set2 () die jeweiligen Großen der lokalen Diskretisierungssterne abfragen.
Neben diesen Platzhaltern fur reellwertige Basisfunktionen existieren weitere Platzhalter fur zwei-
bzw. dreidimensionale Vektorbasisfunktionen. Diese lauten v vec2D(), w vec2D() bzw. v vec3D(), w vec3D(),
und besitzen eigene Berechnungsmethoden zur internen Bestimmung der Vektorfunktionen auf trans-
formierten Elementen aus den Basisfunktionen des Referenzelements.
11.3.2 Differentialoperatoren
Die in Colsamm implementierten Differentialoperatoren beschranken sich auf Operatoren erster Ord-
nung, welche fur die Realisierung einer Vielzahl von Problemen ausreichend sind. Neben dem Gradien-
ten grad (.) kann zur kann zur effizienteren Berechnung in 2D ein spezieller Operator grad2D(.) verwendet
werden. Des Weiteren sind die jeweiligen partiellen Ableitungen auch einzeln anwendbar: d dx (.) , d dy(.)
und d dz (.) . Fur vektorwertige Basisfunktionen sind die Rotationsoperatoren curl2D(.) und curl3D(.) de-
finiert.
In diese Operatoren konnen lediglich die vorher aufgefuhrten Platzhalter fur Basisfunktionen einge-
setzt werden. Da die zugrunde liegenden Ableitungen der Basisfunktionen durch die Anwendung der
Kettenregel mittels einer Matrix-Vektor-Multiplikation bereits intern vorberechnet werden, wurden
andere Terme in diesen Differentialoperatoren zu zusatzlichen Berechnungen und damit zu einem
Performance-Verlust fuhren. Doch auf Grund der Linearitat der Ableitung konnen Terme in den Dif-
ferentialoperatoren im Allgemeinen entsprechend umgeformt werden.
Zusatzlich zu diesen Differentialoperatoren erster Ordnung existieren fur die Randelemente noch
Funktionen zur Bestimmung der Normalenvektoren N () und der Einheitsnormalenvektoren N Unit().
11.3.3 Konstanten, Polynome, Vektoren
Die Differentialoperatoren konnen durch die ublichen Rechenoperationen mit beliebigen Konstanten
kombiniert werden, lediglich bei der Definition von Vektoren sind Konstanten mittels C (.) anzugeben.
Daruber hinaus konnen auch Polynome eingefuhrt werden (mittels x , y und z ), sowie die Exponential-
funktion (Exp(.)), die Quadratwurzel (Sqrt (.) ) oder die allgemeine Potenz (Pow(.,.)). Bei der Verwendung
von komplexwertigen Basisfunktionen, Ausdrucken oder Funktionen kann mittels der Funktion Conj(.)
das konjugiert Komplexe berechnet werden.
Zusatzlich konnen Vektoren definiert werden die aus Konstanten, Ausdrucken oder Funktionen be-
stehen und mit anderen Vektoren, Gradienten oder Vektorbasisfunktionen addiert, subtrahiert oder
multipliziert werden. Diese Vektoren werden durch Angabe der Komponenten, jeweils getrennt durch
den Operator &, implementiert. Beispielsweise wird der Vektor(
1, ∂v∂x , x
)
in Colsamm folgender-
maßen definiert:
( C (1) & d dx ( v ( ) ) & x <1>() )
94
KAPITEL 11. COLSAMM
Daruber hinaus konnen Daten an den Freiheitsgraden des aktuellen Elements in den Integranden
eingefuhrt werden. Dafur sind die zugehorigen Daten in Arrays (in lokaler Nummerierung) abzuspei-
chern, und bei der Integration in Kombination mit den entsprechenden Basisfunktionen einzusetzen.
Enthalt z.B. das Feld data elementspezifische Werte an den Freiheitsgraden eines Elements, so kann
mittels
vec ( data ) ∗ v ( )
die Multiplikation der Basisfunktionen mit diesen Werten durchgefuhrt werden.
11.3.4 Benutzerdefinierte, veranderliche Funktionen
In Erganzung zu der in Abschnitt 11.3.3 erlauterten Methode Funktionen einzufuhren, wird die Ver-
wendung von benutzerdefinierten Funktionen angeboten. Die Funktion
double f u n c t i o n ( double x , double y , double z ) . . .
wird mittels func( function ) in Integranden eingefuhrt. Wird dabei eine Funktion verwendet, die eine
geringe Anzahl an Parametern besitzt als die Dimension des Referenzelements, so werden nur die x-
bzw. y-Koordinaten in der Funktion verwendet. Generell ist jedoch zu beachten, dass es sich bei dieser
Methode um die Einfuhrung von Call-Back-Funktionen handelt, die, wie bereits in Abschnitt 4.2.2
angesprochen, bei intensiver Nutzung einen Performanceverlust zur Folge haben. Aus diesem Grund
sollten Funktionen vorrangig mittels der Mechanismen aus Abschnitt 11.3.3 definiert werden.
11.4 Definition neuer Finiter Elemente
Zur Definition neuer Finiter Elemente werden Ableitungen der Klasse Domain eingefuhrt (siehe Anhang
B.2), wobei zur Vereinfachung sog. Zwischenelemente existieren (z.B. Simple Element, Vectorial Element).
Prinzipiell werden neben der entsprechenden Festlegung der Template-Parameter der Basisklasse auch
die Formeln der Basisfunktionen auf dem Referenzelement benotigt. Im Folgenden werden die zwei
wesentlichen Aspekte der Einfuhrung neuer Finiter Elemente genauer erlautert, die Definition der
Transformationsformeln und die Erstellung der Basisfunktionen.
11.4.1 Transformationen
Die Transformationen definieren die Abbildung der Referenzelemente auf beliebige Elemente mit ent-
sprechender Geometrie, wie bereits in Abschnitt 10.1.4 erlautert. Danach setzen sich die Formeln dieser
Transformationen aus den Kombinationen der Diskretisierungspunkten des Elements K multipliziert
mit den raumlichen Koordinatenfunktionen zusammen. Beispielsweise lautet die Abbildung fur Tetra-
eder mit den Eckpunkten (PK0 , PK
1 , PK2 , PK
3 )
TK(x, y, z) = PK0 + (PK
1 − PK0 )x + (PK
2 − PK0 )y + (PK
3 − PK0 )z.
Da die Berechnungen zu dieser Transformation fur jedes Element durchgefuhrt werden, basiert die zu-
grunde liegende Implementierung auf der leistungsfahigen FET-Technik (siehe Abschnitt 6.2). Dadurch
sind sehr effiziente Auswertungen der Transformationsabbildung moglich. Daruber hinaus werden in
den meisten Fallen keine ausdrucksabhangigen Konstanten benotigt, so dass die template-nummerierte
Einfuhrung von Werten im Allgemeinen nicht benotigt wird. Durch diese Art der Definition konnen die
Formeln als Typen gespeichert werden, und somit als Template-Parameter fur die Einfuhrung neuer
Elemente verwendet werden.
Zur Implementierung der obigen Transformationsformel in Colsamm werden die raumlichen Koor-
dinatenfunktionen x, y, und z durch U(), V() bzw. W() ersetzt. Die Speicherung der entstehenden
95
11.4. DEFINITION NEUER FINITER ELEMENTE
FET-Ausdrucke wird mit dem Define Transformation-Makro durchgefuhrt. Dieses erhalt die Transfor-
mationsformel sowie den gewunschten Namen als Parameter (siehe Abschnitt 7.3). Damit sieht die
Implementierung der obigen Transformationsformel fur Tetraeder folgendermaßen aus:
Listing 11.2: Implementierung der Tetraeder-Transformationsformel.
De f i n e T r an s f o rma t i on (
P 0 ( ) + ( P 1 ( ) − P 0 ( ) ) ∗ U () +
( P 2 ( ) − P 0 ( ) ) ∗ V () +
( P 3 ( ) − P 0 ( ) ) ∗ W() ,
Te t r a hed r on Tran s f o rma t i on ) ;
Bei der Realisierung der Transformationen wurde besonders darauf geachtet, dass die jeweiligen For-
meln fur den Benutzer spezifisch definierbar sind. Je nach Problemstellung oder geometrischer Vorstel-
lung sind die Elemente im Diskretisierungsgitter unterschiedlich nummeriert. Die allgemeinen Defini-
tionen von Transformationsformeln in Colsamm erlauben es Anwendern, sehr intuitiv eigene Formeln
einzufuhren.
Auf die gleiche Weise konnen beispielsweise auch Transformationen fur isoparametrische Elemente
definiert werden, siehe Abbildung 10.3. Die Konstanten in den Transformationsformeln mussen aller-
dings mittels der Funktion C<.>(.) in FET-Typen gekapselt werden, wobei in den spitzen Klammern
eine eindeutige Nummerierung zu stehen hat. Damit kann die isoparametrische Transformation aus
Abschnitt 10.1.4
T K(x, y) = PK0 + (PK
1 − PK0 )x + (PK
3 − PK0 )y + (4PK
2 − 2(PK1 + PK
3 ))xy
wie folgt programmiert werden:
Listing 11.3: Transformationsformel fur FE-Dreiecke mit einer krummlinigen Seite.
De f i n e T r an s f o rma t i on (
P 0 ( ) + ( P 1 ( ) − P 0 ( ) ) ∗ U () +
( P 3 ( ) − P 0 ( ) )∗ V () +
( C<1>(4) ∗ P 2 ( ) − C<2>(2) ∗ ( P 1 ()+P 3 ( ) ) ) ∗ U ()∗ V ( ) ,
T r i a n g l e T r a n s f o r ma t i o n I s o ) ;
11.4.2 Basisfunktionen
Die Definition von Basisfunktionen findet entweder im Konstruktor der neu eingefuhrten Finiten Ele-
mente oder durch externe Methodenaufrufe statt. Dazu werden die verschiedenen Varianten der Funk-
tion Set (.) verwendet (genauer beschrieben in Abschnitt B.2). Da die Basisfunktionen intern abgespei-
chert werden, sind diese mittels der ET-Variante aus Listing 7.7 implementiert. Zur Definition der
Basisfunktionen konnen Terme aus Monomen (X , Y , Z ), Konstanten, Sinus-, Kosinus-, Exponential-
und Wurzelfunktionen, sowie komplexwertige Funktionen verwendet werden.
Listing 11.4: Definition eines Tetraederelements in Colsamm.
s t r u c t Tet r ahed r on : pub l i c Simple Element <4,D3 ,
Tet rahedron Trans f o rmat i on , 4 , Un i t Tet rahedron >
Tet r ahed r on ()
t h i s−>Set ( 1 . − X (1) − Y (1) − Z (1) ) ;
t h i s−>Set ( X (1) ) ;
t h i s−>Set ( Y (1) ) ;
t h i s−>Set ( Z (1) ) ;
;
Eine vollstandige Definition eines Tetraederelements mit linearen Basisfunktionen und der bereits
angefuhrten Transformationsformel ist in Listing 11.4 dargestellt. Darin legen die Template-Parameter
96
KAPITEL 11. COLSAMM
bei der Ableitung von Simple Element gewisse Eigenschaften des Referenzelements fest. Entsprechend
der in Listing 11.4 auftretenden Reihenfolge der Template-Argumente sind dies: die Eckpunkte des
Elements (4), die Dimension (D3), die Transformationsformel (Tetrahedron Transformation), die Anzahl der
Basisfunktionen (4), sowie die Geometrie des Referenzelements (Unit Tetrahedron).
Um die flexible Nutzung von Colsamm zu demonstrieren, werden nachfolgend in der gleichen Weise
auch die Nedelec-Elemente aus Abschnitt 10.2.4 definiert. Dabei werden die vektorwertigen Basisfunk-
tionen mittels fVec (.) eingefuhrt. Zusatzlich ist fur jede Basisfunktion die Kante anzugeben, auf der sie
nicht-verschwindende Tangentialkomponenten besitzt (siehe auch Abbildung 10.4). Zusammen mit der
Transformation werden die sechs vektorwertigen Basisfunktionen
λ01 =
1 − y − z
x
x
, λ02 =
y
1 − x − z
y
, λ03 =
z
z
1 − x − y
,
λ12 =
y
−x
0
, λ13 =
z
0
−x
, λ23 =
0
z
−y
,
auf dem Referenztetraeder wie in Listing 11.5 implementiert.
Listing 11.5: Definition von Nedelec-Elementen erster Ordnung in Colsamm.
s t r u c t Vec to r Te t r a hed r on
: pub l i c Vec to r i a l E l emen t <4,D3 , Tet rahedron Trans f o rmat i on ,
6 , Un i t Tet rahedron >
Vec to r Te t r a hed r on ( )
t h i s−>Set ( fVec ( 1 − Y () − Z ( ) , X ( ) , X ( ) ) , 0 , 1 ) ;
t h i s−>Set ( fVec ( Y ( ) , 1 − X () − Z ( ) , Y ( ) ) , 0 , 2 ) ;
t h i s−>Set ( fVec ( Z ( ) , Z ( ) , 1 − X () − Y () ) , 0 , 3 ) ;
t h i s−>Set ( fVec ( Y ( ) , − X () , N u l l ( ) ) , 1 , 2 ) ;
t h i s−>Set ( fVec ( Z ( ) , N u l l ( ) , − X () ) , 1 , 3 ) ;
t h i s−>Set ( fVec ( Nu l l ( ) , Z ( ) , − Y () ) , 2 , 3 ) ;
;
11.5 Anwendungsbeispiele
Nach den Erlauterungen zur Benutzung von Colsamm werden im Folgenden einige praktische Anwen-
dungen der Template-Bibliothek betrachtet. Durch die einfachen und intuitiven Schnittstellen und den
klar definierten Funktionsumfang eignet sich Colsamm fur die Diskretisierung von partiellen Differen-
tialgleichungen in unterschiedlichsten Anwendungsgebieten.
Eine sinnvolle Rolle spielt Colsamm beispielsweise in der Lehre. So wird Studenten ein einfacher
Zugang zur Finiten Elemente Methode ermoglicht, ohne die zugrunde liegenden mathematischen Pro-
blemstellungen selbst implementieren zu mussen. Dadurch konnen bereits fruh komplexe Aufgabenstel-
lungen gelost werden, und somit das Verstandnis fur die FEM besser vertieft, sowie deren Moglichkeiten
leichter vermittelt werden.
Praxisrelevante Einsatzgebiete finden sich beispielsweise die Lasersimulation, wo Colsamm benutzt
wird, um die Ratengleichungen der Photonendichten in Halbleiterlasern zu diskretisieren. Eine wei-
tere Anwendung entstand in der Bibliothek ParExPDE [CBHR], die unter anderem zur numerischen
Simulation von Partikelbewegungen in Flussigkeiten verwendet wird. Zusatzlich dient Colsamm im Si-
mulationspaket des ORCAN-Software-Frameworks [THR05, Fra05] zur einfachen Diskretisierung von
passenden Problembeispielen. Nachfolgend werden zwei Anwendungen der Template-Bibliothek Col-
samm genauer beschrieben.
97
11.5. ANWENDUNGSBEISPIELE
11.5.1 Finite Elemente im optischen Fluss
Der optische Fluss beschreibt die Bewegung von Bildpunkten in einer Folge von Bildern [Bru06]. Die
mathematische Problemstellung besteht dabei in der Berechnung eines approximierenden Bewegungs-
vektorfeldes u : Rd → Rd mit d ∈ 2, 3 aus zwei oder mehr aufeinander folgenden Bildern einer
d-dimensionalen Video-Sequenz,
I : Rd × T → R mit d, tmax ∈ N und T = [0, . . . , tmax].
Darin beschreibt I die Intensitat des Grauwerts fur jeden Punkt im Bildgebiet Ω und dem Zeitgebiet
T. Zusatzlich wird angenommen, dass sich die Grauwerte der bewegenden Bildobjekte nicht verandern,
d.h.
I(x, t) = I(x + u, t + 1) . (11.1)
gilt fur jeden Punkt (x, t) ∈ Rd×T. Diese Gleichung wird durch eine Taylor-Entwicklung und weglassen
der Terme hoherer Ordnung linearisiert. Damit erhalt man
uTt ∇tI ≈ 0 , (11.2)
wobei z.B. im zweidimensionalen ∇tI = (Ix, Iy, It) die Raum-Zeit-Ableitung und ut = (u, v, 1) das
Bewegungsfeld bezeichnet. Um den optischen Fluss ut zu berechnen, versucht man das Integral
D(∇tI,ut) =
∫
Ω
(uTt ∇tI)2dx (11.3)
uber alle Punkte des Bildgebiets Ω zu minimieren. Dabei D wird Datenterm genannt.
Falls der Term (11.3) an einigen Stellen im Gebiet verschwindet, kann dort mit der Bedingung
(11.2) alleine der optische Fluss nicht berechnet werden. Um dennoch eine eindeutige Losung auf
dem gesamten Gebiet zu bekommen, muss das Problem regularisiert werden. Dies kann beispielsweise
durch diffusionsbasierte Modelle erreicht werden, bei denen angenommen wird, dass sich der optische
Fluss lokal nur sehr wenig verandert, d.h. der Gradient des Vektorfeldes klein bleibt. Beispiele von
Regularisierern sind:
• der homogene Diffusions-Regularisierer (HD) [HS81]
S1(u) =
∫
Ω
d∑
i=1
|∇ui|2dx , (11.4)
welcher die Information homogen im gesamten Gebiet streut,
• ein isotroper Diffusions-Regularisierer (ID) [AELS99]
S2(u) =
∫
Ω
g(
|∇I|2)
d∑
i=1
|∇ui|2dx , (11.5)
mit
g(s2) =1
√
1 + s2/ǫ2, ǫ > 0 (11.6)
welcher in der Nahe von Bildkanten die Diffusion reduziert, um das Bewegungsfeld an den Kanten
nicht zu verwischen, und
• ein anisotroper Diffusions-Regularisierer (AD) [Nag83]
S3(u) =
∫
Ω
d∑
i=1
∇uTi T (∇I)∇uidx , (11.7)
98
KAPITEL 11. COLSAMM
Abbildung 11.3: Frames 8 und 9 der bekannten Yosemite-Sequenz.
welcher den isotropischen Regularisierer erweitert, um das Glatten entlang der Kanten zu ermogli-
chen, jedoch nicht uber die Kante hinweg. Dabei ist
T (∇I) =1
‖∇I‖2 + 2ǫ2(∇I⊥∇I⊥T + ǫ2Id) , ǫ > 0 , (11.8)
wobei Id die Einheitsmatrix darstellt.
Der Datenterm kann mit den unterschiedlichen Regularisierern in einem Energiefunktional kombiniert
werden
Ei(u) :=
∫
Ω
D(∇tI,ut) + αSi(u)dx , i ∈ 1, 2, 3, (11.9)
das es zu minimieren gilt. Eine notwendige Bedingung fur die Minimierung der Gleichung (11.9) stellt
die Losung der Euler-Lagrange-Gleichung
0 = ∇(uTt ∇tI) − α∇T (σi∇u) , i ∈ 1, 2, 3 (11.10)
dar, wobei σi die Diffusionstensoren beschreibt, welche fur die drei Regularisierer durch σ1 = Id,
σ2 = g(|∇I|2)Id und σ3 = T (∇I) definiert sind. Colsamm wird eingesetzt um die Euler-Lagrange-
Gleichungen zu diskretisieren.
Durch die Diskretisierung entsteht ein lineares System der Form
Ahuh = fh . (11.11)
Dabei beschreibt Ah ∈ MN×N(R) die System-Matrix, uh ∈ RN den zu bestimmenden Losungsvektor
und fh ∈ RN den Vektor der rechten Seite auf dem Gebiet Ωh. Zur schnellen Berechnung der Losung
werden Mehrgitterverfahren verwendet [TOS01, Bru06]. Die anschließende Visualisierung stellt die
Vektoren des Bewegungsfeldes in jedem Punkt dar.
Ublicherweise werden die Gleichungen mit der Finiten Differenzen Methode diskretisiert. Wird statt-
dessen Finite Elemente Methode eingesetzt, so entstehen großere Diskretisierungssterne, die mehr
Speicherbedarf und aufwandigere Algorithmen zur Folge haben. Andererseits resultieren aus diesen
FE-Approximationen aber stabilere Diskretisierungen.
Als zweidimensionales Beispiel wird in Abbildung 11.3 die bekannte Yosemite-Sequenz gezeigt. Dazu
prasentiert Abbildung 11.4 das Vektorfeld des optischen Flusses bzgl. beiden Yosemite-Bilder, resul-
tierend aus der FE-Diskretisierung uber Colsamm. Die linke Grafik zeigt das resultierende Vektorfeld
unter Verwendung des anisotropen Diffusions-Regularisierers, wahrend bei der rechten Grafik der ho-
mogene Diffusions-Regularisierer benutzt wurde. Dabei ist zu erkennen, dass der anisotrope Ansatz die
Bewegungen genauer erfasst als die homogene Regularisierung.
Als weiteres Anwendungsbeispiel zeigt Abbildung 11.5 verschiedene Geschwindigkeitsfelder einer 3D-
Sequenz von Herzbewegungen. Die Zeilen enthalten die transversal, sagittal und coronal Ansichten der
99
11.5. ANWENDUNGSBEISPIELE
Abbildung 11.4: Optisches Flussfeld fur Frame 8 und 9 der Yosemite-Sequenz, visualisiert mittels LIC
(line integral convolution) [CL93, SK98, SK97] mit dem anisotropen Diffusions-Regularisierer (linke
Grafik) und dem homogenen Diffusions-Regularisierer (rechte Grafik).
Norm der Geschwindigkeitsfelder unter Verwendung aller drei erwahnten Regularisierer. Dabei stellt
sich der isotrope Diffusion-Regularisierer (mittlere Spalte) als qualitativ beste Variante heraus.
11.5.2 Quell-Lokalisation in Dipolmodellen
Ein zweites Anwendungsbeispiel der Template-Bibliothek Colsamm findet sich in den Quellenberech-
nungen der Dipolmodelle von Hirnstromen im Software-Paket NeuroFEM [Koo03], veroffentlicht in
[WKM+06b]. Im Allgemeinen wird in der Elektroenzephalografie (EEG) zur inversen Bestimmung der
Primarstromquellen ein mathematisches Dipolmodell herangezogen, um die zugrunde liegende Strom-
verteilung im Gehirn zu beschreiben. Diese inversen Methoden basieren auf der Losung des zugehorigen
Vorwartsproblems, welches die Simulation des elektrischen Potenzials des Kopfes als Volumenleiter fur
einen Dipol in der Cortex-Schicht des menschlichen Gehirns beinhaltet.
Die zugrunde liegenden Stromungen im Gehirn werden durch die Maxwell-Gleichungen beschrieben
[PH67]. Es bezeichnen E und D das elektrische Feld sowie die elektrische Flussdichte, ρ die elektri-
sche Ladungsdichte, ǫ die elektrische Leitfahigkeit und j die elektrische Stromdichte. Des Weiteren
beschreibt µ die magnetische Leitfahigkeit und H bzw. B bezeichnen die magnetische Feldstarke bzw.
die magnetische Flussdichte. In der entsprechenden niedrigen Frequenz (kleiner als 2 MHz) konnen der
Gewebewiderstand, der induktive Effekt und der elektromagnetische Ausbreitungseffekt vernachlassigt
werden. Weiter kann angenommen werden, dass µ auf dem gesamten Gebiet konstant ist und gleich
der magnetischen Leitfahigkeit im Vakuum [PH67]. Mit diesen Bezeichnungen und Annahmen lassen
sich die quasi-statischen Maxwell-Gleichungen wie folgt schreiben
∇ · D = ρ
∇× E = 0
∇× H = µj (11.12)
∇ · B = 0. (11.13)
mit den Material-Gleichungen (da sich biologisches Gewebe wie ein Elektrolyt verhalt)
D = ǫE
B = µH.
Das elektrische Feld kann als negativer Gradient eines skalaren Potenzials dargestellt werden,
E = −∇Φ. (11.14)
100
KAPITEL 11. COLSAMM
(a) Trans. Schnitt 126 (HD). (b) Trans. Schnitt 126 (ID). (c) Trans. Schnitt 126 (AD).
(d) Sag. Schnitt 111 (HD). (e) Sag. Schnitt 111 (ID). (f) Sag. Schnitt 111 (AD).
(g) Cor. Schnitt 112 (HD). (h) Cor. Schnitt 112 (ID). (i) Cor. Schnitt 112 (AD).
Abbildung 11.5: Visualisierung der Norm des berechneten Geschwindigkeitsfeld einer Sequenz von
Herzbewegungen bzgl. transversalen (erste Zeile), sagittalen (zweite Zeile), und coronalen (dritte Zeile)
Ansichten. Verwendet wurden der homogene Diffusions-Regularisierer (HD), der isotrope Diffusions-
Regularisierer (ID) und der anisotrope Diffusions-Regularisierer (AD).
Weiter spaltet sich im Bioelektromagnetismus die elektrische Stromdichte in zwei Teile auf, die sog.
primare und die sekundare Stromdichte, bezeichnet mit jp bzw. σE,
j = jp + σE, (11.15)
wobei σ einen 3 × 3 Leitfahigkeitstensor darstellt.
Bei der Beschreibung des Vorwartsproblems entsteht durch Divergenzbildung in Gleichung (11.12)
und unter Verwendung der Gleichungen (11.14) und (11.15) die Poisson-Gleichung
∇ ·(
σ∇Φ)
= ∇ · jp = Jp in Ω. (11.16)
Diese beschreibt die Verteilung des Potenzials im (Kopf-)Gebiet Ω bzgl. einer primaren Stromdichte jp
101
11.5. ANWENDUNGSBEISPIELE
in der Cortex-Schicht des menschlichen Gehirns. Die zugehorigen Randbedingungen auf der Oberflache
Γ lauten(
σ1∇Φ1,n)∣
∣
∣
Γ=
(
σ2∇Φ2,n)∣
∣
∣
Γ
wobei n die Einheitsnormale der Oberflache beschreibt, welche die Stetigkeit der Stromdichte bzgl.
der Oberflachenregionen mit verschiedenen Leitfahigkeiten ausdruckt. Fur die Kopfoberflache Γ = ∂Ω
ergeben sich homogene Neumann-Randbedingungen,
(
σ∇Φ,n)∣
∣
Γ= 0. (11.17)
Zusatzlich benotigt man noch eine Referenz-Elektrode mit einem vorgegebenen Potenzial, d.h.
Φref = 0 . (11.18)
Die primaren Stromdichten beschreiben die Ionenbewegungen innerhalb der Dendriten von großen
pyramidrischen Zellen der aktivierten Regionen in der Cortex-Schicht. Bei der Neuronengenerierung
mittels evozierter Felder ist bereits in kleinen Abstanden zur aktivierten Region ausschließlich das
Dipolmoment der Quelle sichtbar [WKM+06b]. Dieser sog. mathematische Dipol jp := Mδ(x − x0) an
der Stelle x0 ∈ R3 mit dem Moment M ∈ R3 kann geschrieben werden als
Jp(x) = ∇ · jp (x) := ∇ · Mδ(x− x0) . (11.19)
Zur Formulierung des Subtraktions-Ansatzes [WKM+06b] wird angenommen, dass ein nicht-leeres
Teilgebiet Ω∞ ⊂ Ω um die Quelle x0 mit homogener konstanter Leitfahigkeit σ∞ existiert, so dass
x0 ∈ Ω∞ / ∂Ω∞ gilt. Bei der Substraktionsmethode wird die Leitfahigkeit σ in zwei Teile gespaltet
σ = σ∞ + σcorr, (11.20)
so dass σ∞ im gesamten Gebiet Ω konstant ist und fur σcorr in einem kleinen Teilgebiet Ω∞ σcorr(x) =
0, ∀x ∈ Ω∞ gilt. Ebenso wird auf diesen Gebieten das Gesamtpotenzial aufgespalten:
Φ = Φ∞ + Φcorr. (11.21)
Dabei stellt das Singularitatspotenzial Φ∞ die Losung eines Dipols in einem unbeschrankten homogenen
Leiter mit konstanter Leitfahigkeit σ∞ dar. Im Falle einer anisotropen Leitfahigkeit in Ω∞ wird das
Singularitatspotenzial beschrieben durch die Funktion [MS99]
Φ∞(x) =1
4π√
detσ∞
〈 ~M, (σ∞)−1(~x − ~x0)〉(〈(σ∞)−1(~x − ~x0), (~x − ~x0)〉)3/2
. (11.22)
Subtrahiert man die Poisson-Gleichung ∆Φ∞ = Jp · (σ∞)−1 von der Gleichung (11.16), erhalt man
eine Poisson-Gleichung fur das Korrektur-Potenzial:
−∇ ·(
σ∇Φcorr)
= f in Ω, f = ∇ ·(
σcorr∇Φ∞)
mit inhomogenen Neumann-Randbedingungen auf der Oberflache
σ∂Φcorr
∂n= g auf Γ, g = −σ
∂Φ∞
∂n.
Zur numerischen Berechnung von Φcorr kann das skalare Potenzial Φ uber die Gleichung (11.21) be-
stimmt werden. Durch diesen Subtraktions-Ansatz wird die Singularitat der rechten Seite von Gleichung
(11.23) eliminiert, da in Ω∞ das Korrektur-Potenzial σcorr verschwindet. Damit ist die gesamte rechte
Seite auf Ω∞ gleich Null.
102
KAPITEL 11. COLSAMM
Zur numerischen Simulation von realistischen Kopfmodellen wird die Finite Elemente Methode ver-
wendet, da dadurch beliebige Geometrien sowie die anisotropen Materialeigenschaften direkt erfasst
werden konnen. Dazu wird zunachst die rechte Seite mittels partieller Integration umgeformt:
〈l, v〉 =
∫
Ω
fvdΩ +
∫
Γ
gvdΓ = −∫
Ω
∇v σcorr∇Φ∞dΩ −∫
Γ
〈σ∞∇Φ∞,n〉v dΓ (11.23)
Damit erhalt man die Formulierung∫
Ω
〈σ∇u,∇v〉dΩ = −∫
Ω
∇v σcorr∇Φ∞dΩ −∫
Γ
〈σ∞∇Φ∞,n〉v dΓ . (11.24)
Die Assemblierung der resultierenden Diskretisierungsmatrizen basiert auf Colsamm, wobei in der
aktuellen Realisierung lediglich die Randintegrale mittels der Template-Bibliothek berechnet werden.
Abbildung 11.6: Links: Schnitt durch das Tetraeder-Finite-Elemente Kopf-Modell mit EEG- (Elek-
troenzephalografie) Elektroden. Dabei sind die Oberflachen der inneren und außeren Schadeldecke
angedeutet. Rechts: Verteilung des elektronischen Potenzials einer quasi-radialen Dipolquelle in der
primaren Cortex-Schicht. Berechnet wurde diese Dipolquelle mittels eines Drei-Schichten-Ansatzes
(Haut, Schadel, Gehirn) eines realistischen Kopfmodells, diskretisiert durch Hexaedergitter.
11.6 Entwicklung und Zusammenfassung
Die Template-Bibliothek Colsamm dient als Beitrag zur einfachen Realisierung von FE-Diskretisie-
rungen. Durch den grundsatzlichen Anspruch, keine Einschrankungen an die zugrunde liegenden Dis-
kretisierungsgitter zu stellen, ergaben sich Einsatzmoglichkeiten in den verschiedensten Gebieten des
wissenschaftlichen Rechnens. Durch die intuitive Implementierung der schwachen Formulierungen der
partiellen Differentialgleichungen kann die FEM spezifisch fur die jeweilige Problemstellung angewandt
werden. Somit eignet sich Colsamm nicht nur fur anwendungsorientierte Entwickler, sondern auch fur
die Untersuchung passender Diskretisierungen mit benutzerdefinierten Finiten Elementen. Daruber
hinaus ermoglicht das Programm in der Ausbildung einen einfachen Einstieg in die FEM.
11.6.1 Schnelle Berechnungen der lokalen Steifigkeitsmatrizen
Durch den grundlegenden Aufbau als Template-Bibliothek liefert Colsamm sehr effiziente Programme
und kann somit auch in Hoch- oder Hochstleistungsrechnen eingesetzt werden. Beispielsweise ermoglicht
103
11.6. ENTWICKLUNG UND ZUSAMMENFASSUNG
die elementbezogene Berechnung der lokalen Steifigkeitsmatrizen eine naturliche Parallelisierung der
Assemblierung der gesamten Diskretisierung.
Durch die ausgewogene Wahl der Diskretisierungseigenschaften, die in Template-Parameter gekapselt
sind, entstand eine leistungsstarke und dennoch flexible Bibliothek. Dadurch konnen Entscheidungen
zur Laufzeit vermieden werden, da bereits wahrend des Compilierens die entsprechende Programmie-
rung instantiiert wird. Dies ermoglicht daruber hinaus entscheidende Optimierungen wie etwa Inlining.
Trotz der als Templates gekapselten Eigenschaften konnen die Finite Elemente durch die Template-
Vererbung sehr intuitiv implementiert und verwendet werden. Dazu dienen vor allem die in den Kapi-
teln 5, 6 und 7 entwickelten ET-Techniken, um effiziente Realisierungen der uberladenen Operatoren
zu erhalten. Diese finden in der Implementierung der Transformationsformeln sowie bei der Defini-
tion der Basisfunktionen Anwendung. Auch die Programmierung der schwachen Formulierungen im
Anwendercode wird durch leistungsstarke ET-Implementierungen unterstutzt.
Neben diesen programmiertechnischen Grundlagen basiert die Leistungsfahigkeit von Colsamm vor
allem auf dem wohlstrukturierten Programmaufbau. Die Aufteilung der lokalen FE-Diskretisierung in
drei Stufen verhindert unnotige Berechnungen bereits aktualisierter Werte. Dabei werden bei der In-
itialisierung der Elemente alle Auswertungen bzgl. des Referenzelements durchgefuhrt und gespeichert.
In ahnlicher Weise werden nach dem Setzen der Diskretisierungspunkte die transformationsspezifischen
Werte berechnet und intern gesichert. Zuletzt werden bei der Integration die gespeicherten Werte ent-
sprechend der schwachen Formulierung die lokalen Diskretisierungssterne zusammengestellt. So konnen
beispielsweise mehrere Steifigkeitsmatrizen auf einem Element berechnet werden, ohne jedesmal die zu-
gehorigen Werte der Transformation bestimmen zu mussen.
Generell bietet somit Colsamm die Voraussetzungen, um effiziente Assemblierungen der lokalen
Diskretisierungsmatrizen durchzufuhren. Dabei ist naturlich zu berucksichtigen, dass in strukturier-
ten Gittern und translationsunabhangigen Integranden die Diskretisierungssterne durch entsprechende
Skalierung schneller berechnet werden konnen. In den allgemeinen Fallen werden jedoch die gleichen
Berechnungsschritte wie in Colsamm benotigt.
11.6.2 Erweiterungen und Ausblick
In der aktuellen Realisierung konnen mittels Colsamm Differentialoperatoren erster Ordnung diskre-
tisiert werden. Dadurch wird bereits eine große Anzahl der praxisrelevanten Problemstellungen abge-
deckt. Daruber hinaus treten beispielsweise in der schwachen Formulierung der biharmonischen Glei-
chung [GR05]∫
Ω
∆u∆v dµ =
∫
Ω
fv dµ
Differentialoperatoren zweiter Ordnung auf. Diese konnen prinzipiell mit den bereits in Colsamm rea-
lisierten Mechanismen behandelt werden, zusatzlich sind jedoch die zweiten Ableitungen der Basis-
funktionen und der Transformation zu berechnen. Die Bestimmungen und Auswertungen der zweiten
Ableitungen der Basisfunktionen ergeben keinen Performance-Nachteil. Da jedoch die Berechnung der
Transformationswerte unabhangig vom ubergebenen Integranden geschieht, wurde die Hessematrix
der Transformation berechnet, selbst wenn diese nicht benotigt wird. Dies wurde die Leistungsfahig-
keit entscheidend beeintrachtigen. Um dies zu umgehen musste der Benutzer bereits beim Setzen der
Diskretisierungspunkte festlegen, ob die zweiten Ableitungen benotigt werden. Dies wurde jedoch auch
die Grenzen der einzelnen Berechnungsschritte verwischen.
Zusatzliche Erweiterungen von Colsamm sind fur die Realisierung von C1- bzw. C2-Elementen denk-
bar. In den zugehorigen Finiten Elementen existieren dadurch Basisfunktionen, die als Freiheitsgrad
nur einen Gradient in einer bestimmten Richtung besitzen. Wahrend diese Ansatze fur strukturier-
te Gitter einfach durchfuhrbar sind, mussen bei gedrehten Elementen die entsprechenden Richtungen
uber Multiplikation mit der Jacobi-Matrix miteinander kombiniert werden, siehe Abschnitt 10.1.4. Da-
zu sind die zusammengehorigen Basisfunktionen bei der Initialisierung im Element zu kennzeichnen.
104
KAPITEL 11. COLSAMM
Außerdem wird bei nicht-affinlinearen Abbildungen wie vorher die Hesse-Matrix der Transformation
benotigt, um die vollstandige Berechnung der Kettenregel durchfuhren zu konnen.
Trotz dieser moglichen Erweiterungen stellt Colsamm bereits jetzt eine sehr vielfaltig einsetzbare
Bibliothek dar, mittels der viele der bestehenden Finiten Elemente Diskretisierungen durchgefuhrt
werden konnen. Dies soll vor allem auch Anwender aus Anwendungsgebieten darin unterstutzen, die
Finite Elemente Methode zur approximativen Losung der eigenen PDG einsetzen zu konnen, ohne sich
mit den mathematischen Details befassen zu mussen.
105
11.6. ENTWICKLUNG UND ZUSAMMENFASSUNG
106
KAPITEL 12. WEITERE EINSATZGEBIETE VON ET
12 Weitere Einsatzgebiete von ET
Nach den Erlauterungen zur Template-Bibliothek Colsamm, die einen Großteil der in Teil II entwickel-
ten Techniken verwendet, werden in diesem Kapitel weitere Einsatzmoglichkeiten von ET zum Losen
partieller Differentialgleichungen prasentiert. Zunachst wird eine einfache Anwendung diskutiert, die
jedoch demonstriert, wie unterschiedliche ET-Implementierungen kombiniert werden.
12.1 Mengenalgebren mittels ET
Die Grundgebiete der Differentialgleichungen werden im Allgemeinen uber Funktionen, welche die
Rander beschreiben, modelliert. Damit kann entschieden werden, ob ein Punkt innerhalb oder außer-
halb des Gebietes liegt. Diese Beschreibung der Gebiets und der Rander wird zur Generierung des
Diskretisierungsgitters und zur Initialisierung der Variablen mit den Randwerten benotigt. Daneben
werden beispielsweise zur Erstellung adaptiver Gitter Konstuktionen von Teilgebieten benotigt. Eine
leistungsstarke und einfach anwendbare Bibliothek zur Definition und Verknupfung solcher Mengen
kann mittels ET realisiert werden. Dies wurde zum Teil bereits in der Bibliothek ExPDE [Pfl01] ein-
gesetzt, doch wird nachfolgend ein allgemeinerer Ansatz diskutiert.
Eine entsprechende Realisierung besteht aus zwei verknupften ET-Implementierungen. Zunachst
werden Mechanismen zum Beschreiben von Funktionen eingefuhrt, mittels derer Polynome, trigono-
metrische Funktionen, etc. gebildet werden konnen. Da diese Funktionen zur Definition von Gebieten
eingesetzt werden sollen, die auch abspeicherbar und wieder verwendbar sind, wird dazu eine ET-
Implementierung mit vollen Objekten (Abschnitt 5.4) benutzt. Die Zeilen 1 bis 20 in Listing 12.1
zeigen Teile der zugehorigen Programmierung, unter anderem fur die Monome und die Addition. Die
weiteren Funktionenklassen werden auf die gleiche Weise implementiert. Der Auswertungsoperator
operator() berechnet aus den Daten des ubergebenen Feldes das Resultat der jeweiligen Funktion.
Zusatzlich zu diesen Funktionen werden die Testvorschriften, die bestimmen ob ein Punkt innerhalb
des Gebiets liegt, ebenfalls mittels ET-Konstrukten implementiert. Dazu wird eine eigene Wrapper-
Klasse (Bool Expr) eingefuhrt, welche die ET-Ausdrucke kennzeichnet, die einen booleschen Wert er-
geben. Davon abgeleitet werden dann Operationsklassen fur die zugehorigen Vergleichsoperationen
definiert. Listing 12.1 enthalt in den Zeilen 23 bis 31 den Programmcode zum “Kleiner“-Operator
(operator<). Die anderen Vergleichsoperatoren werden analog realisiert. Des Weiteren sollen bestehen-
de Gebietsdefinitionen logisch verknupft werden konnen, um daraus neue Gebiete zu erhalten. Dafur
sind vor allem die Standard-Mengen-Operationen, wie Schnitt, Vereinigung oder Komplementbildung
wichtig. Da das Resultat dieser Operationen wieder Ausdrucke sind, die fur jeden Punkt einen boo-
leschen Wert ergeben, werden diese Operationen auch von Bool Expr abgeleitet. Die Programmierung
der Schnittmenge zweier Gebiete wird in den Zeilen 34 bis 43 von Listing 12.1 gezeigt. Als Operator
fur das Bilden der Schnittmenge wird dabei das logische Und verwendet, weil ein Punkt im Schnitt in
beiden Mengen liegen muss.
Die Benutzung dieser ET-Bibliothek im Anwendercode erfolgt durch die uberladenen Operatoren
wieder ganz intuitiv. Durch ein zusatzlich definiertes Makro Define Domain, das die ET-Objekte in Va-
riablen abspeichert (Abschnitt 7.3), konnen Gebietsdefinitionen gekapselt werden. Bei der Verwendung
sollte jedoch beachtet werden, dass beispielsweise bei Iterationen uber Gitterpunkte die ET-Objekte
nicht in jedem Iterationsschritt neu gebildet werden (Zeilen 4 bis 6 in Listing 12.2). Durch eine wieder-
holte Generierung der zugehorigen ET-Ausdrucke kann sich die Effizienz verschlechtern. Stattdessen
107
12.1. MENGENALGEBREN MITTELS ET
sollte ein Gebiet vor der Schleife in eine neue Variable gespeichert werden (Listing 12.2). Damit wird
die gewunschte Performance erreicht.
Listing 12.1: ET-Implementierung der Test- und Verknupfungsklassen zur Realisierung einer Men-
genalgebra.
template <c l a s s E> s t r u c t Expr ;
template < i n t pos>
c l a s s Monom : pub l i c Expr<Monom <pos> >
i n t exponent ;
5 pub l i c :
Monom ( i n t expo ) : exponent ( expo )
double ope ra to r ( ) ( double∗ data ) const r e t u r n s t d : : pow( data [ pos ] , exponent ) ;
;
i n l i n e Monom <0> x ( i n t expo ) r e t u r n Monom <0>(expo ) ;
10 i n l i n e Monom <1> y ( i n t expo ) r e t u r n Monom <1>(expo ) ;
i n l i n e Monom <2> z ( i n t expo ) r e t u r n Monom <2>(expo ) ;
template <c l a s s L , c l a s s R>
c l a s s Add : pub l i c Expr<Add<L ,R> >
const L l ; const R r ;
15 pub l i c :
Add ( const L& l , const R& r ) : l ( l ) , r ( r )
double ope ra to r ( ) ( double ∗ data ) const r e t u r n l ( data ) + r ( data ) ;
;
template<c l a s s L , c l a s s R>
20 i n l i n e Add<L ,R> ope ra to r + ( const Expr<L>& l , const Expr<R>& r ) ;
template <c l a s s E> s t r u c t Boo l Expr ;
template<c l a s s L , c l a s s R>
c l a s s Less Than : pub l i c Bool Expr<Less Than<L ,R> >
const L l ; const R r ;
25 pub l i c :
Less Than ( const L& l , const R& r ) : l ( l ) , r ( r )
boo l ope ra to r ( ) ( double ∗ data ) const r e t u r n l ( data ) < r ( data ) ;
;
template<c l a s s L , c l a s s R>
30 i n l i n e Less Than<L ,R> ope ra to r < ( const Expr<L>& l , const Expr<R>& r ) ;
template <c l a s s L , c l a s s R>
c l a s s I n t e r s e c t i o n : pub l i c Bool Expr<I n t e r s e c t i o n <L ,R> >
const L l ; const R r ;
pub l i c :
35 I n t e r s e c t i o n ( const L& l , const R& r ) : l ( l ) , r ( r )
boo l ope ra to r ( ) ( double ∗ data ) const r e t u r n l ( data ) & r ( data ) ;
;
template <c l a s s L , c l a s s R>
i n l i n e I n t e r s e c t i o n <L ,R> ope ra to r & ( const Bool Expr<L>& l , const Bool Expr<R>& r ) ;
Der Vorteil dieser ET-Implementierung im Vergleich zu Call-Back-Funktionen besteht in der besseren
Lesbarkeit und Wartbarkeit des Anwendercodes. Die Definitionen der Gebiete befinden sich direkt an
der Stelle des Programms, an der sie verwendet werden. Vergleichbare Implementierungen mit Funktio-
nen haben ihre Definition an anderer Stelle im Programm, oft sogar in anderen Dateien. Daneben sind
fur diese ET-Konstrukte eigene Ausgabe-Operatoren definierbar, die nutzliche Informationen wahrend
des Programmlaufs ausgeben, um so die Fehlersuche zu erleichtern.
Listing 12.2: Verwendung der ET-Mengenalgebra im Anwendercode.
Def ine Domain ( x (1)+y (1) < z (1 ) , domain 1 ) ;
Def ine Domain ( z (2) < 19 , domain 2 ) ;
// Auswertung , G e b i e t s e r s t e l l u n g i n de r S c h l e i f e
f o r ( i n t i = 0 ; i < s i z e ; ++i ) ( domain 1 & ! domain 2 ) [ data ] ;
// Auswertung , G e b i e t s e r s t e l l u n g vor de r S c h l e i f e
Def ine Domain ( domain 1 & ! domain 2 , domain 3 ) ;
f o r ( i n t i = 0 ; i < s i z e ; ++i ) domain 3 [ data ] ;
108
KAPITEL 12. WEITERE EINSATZGEBIETE VON ET
Nach dieser Anwendung von ET-Implementierungen zur Konstruktion von Gebieten, werden im
Folgenden ET eingesetzt, um Iterationsschleifen zu kapseln.
12.2 Iterationsschleifen mittels ET
Die bisher aufgefuhrten ET-Anwendungen waren darauf ausgelegt, vorwiegend mathematische Aus-
drucke zu kapseln, indem mittels der uberladenen Operatoren die zugehorigen Ausdrucksbaume auf-
gebaut wurden. In diesem Abschnitt wird das ET-Konzept dazu verwendet, Iterationsschleifen in ent-
sprechende ET-Objekte umzuwandeln, und diese anschließend auszuwerten. Eine nutzliche Anwendung
findet sich im darauf folgenden Abschnitt 12.3, in dem ein intuitiv anwendbares Schleifenblocking rea-
lisiert wird.
Schleifenubergreifende Optimierungen von ET-Ausdrucken sind bei der Benutzung von C++-Com-
pilern, die direkt Assemblercode erzeugen, kaum oder gar nicht durchfuhrbar. Betrachtet man bei-
spielsweise eine ET-Implementierung fur Vektoren, so ist dabei jede außere Schleife von der Iterati-
onsschleife in der Auswertungsmethode der Vektorklasse getrennt. Somit konnen weder vom Compiler
noch vom Programmierer Schleifenoptimierungen durchgefuhrt werden. Als einfaches Beispiel hierfur
betrachten wir die mittels Finite Differenzen diskretisierte zweidimensionale Poisson-Gleichung auf
dem Einheitsquadrat. Der daraus resultierende Gauß-Seidel-Loser kann mittels entsprechender ET-
Implementierungen folgendermaßen geschrieben werden (N, S, W, E bezeichnen die Richtungen im Dis-
kretisierungsgitter, h die Gitterweite, b die rechte Seite und u den Losungsvektor):
f o r ( i n t i =0; i < i t e r max ; ++i )
u = 0.25 ∗ ( 1 . / ( h∗h ) ∗ b + N(u ) + S(u ) + W(u ) + E(u ) ) ;
Um schleifenubergreifende Operationen zu ermoglichen, muss die Auswertung des Ausdrucks – genau
wie beim herkommlichen ET-Ansatz – ein weiteres Mal verzogert werden. Dafur werden im Folgenden
die ET-Implementierungen zur Beschreibung von Iterationsschleifen prasentiert.
Fur die verzogerte Auswertung definiert die Klasse Evaluate in Listing 12.3 eine Datenstruktur zum
Speichern des Ergebnisvektors
Listing 12.3: Datenstruktur fur die verzogerte Auswertung in Iterationsschleifen.
template <c l a s s R, c l a s s E>
c l a s s Eva l ua t e
R& r e s u l t ; const E& expr ;
pub l i c :
E v a l ua t e (R& re s , const E& e ) : r e s u l t ( r e s ) , expr ( e )
vo id i t e r a t i o n ( ) const r e s u l t = expr ;
;
und des Ausdrucks. Da dem Ergebnisvektor beim Aufruf der Methode iteration das Ergebnis zugewiesen
wird, kann diese Membervariable (R& result) nicht als konstante Referenz deklariert werden.
Um die bisherige Funktionalitat des Zusweisungsoperators der Klasse Vector zu erhalten, wird mit
dem Operator operator== eine weitere Zuweisung definiert. Diese erzeugt aus einem ET-Ausdruck ein
Evaluate-Objekt.
Listing 12.4: Zusweisungsoperator zur Durchfuhrung der verzogerten Auswertung.
c l a s s Vector . . .
template<c l a s s E>
Eva lua te<Vector , E> ope ra to r==(const Expr<E>& expr )
r e t u r n Eva lua te<Vector , E>(∗ t h i s , exp r ) ;
;
109
12.2. ITERATIONSSCHLEIFEN MITTELS ET
Nach diesem Mechanismus zum Verzogern der Auswertung von Vektorausdrucken, werden Implemen-
tierungen fur die Darstellung von Iterationsschleifen mittels ET-Objekten diskutiert. Die Zielsetzung
besteht in einer Konstruktion, die den for-Schleifen in C++ sehr ahnlich ist.
Fur die Realisierung von Iterationsschleifen wird ein Laufindex benotigt. Dazu wird zunachst ein
Wrapper IndexEpr eingefuhrt, der Ausdrucke von Indizes enthalten kann. Die mittes IndexEpr gekapselten
Ausdrucke besitzen alle eine Methode iter , die den aktuellen Indexwert zuruckgibt. Eine passende
Implementierung einer Index-Klasse findet sich in Listing 12.9.
Listing 12.5: Wrapper-Klasse fur Index-Ausdrucke
template <c l a s s E>
s t r u c t I ndexEpr
ope ra to r const E& () const r e t u r n ∗ s t a t i c c a s t <const E∗>( t h i s ) ;
ope ra to r E& () r e t u r n ∗ s t a t i c c a s t <E∗>( t h i s ) ;
;
Eine Schleifendefinition besteht – ganz allgemein betrachtet – aus drei Teilen, einer Initialisierung,
einer Bedingung und einem Ausdruck. Zunachst wird die ET-Implementierung zur Schleifeninitialisie-
rung erlautert (Listing 12.6). Darin werden um den Wrapper LoopInit Initialisierungsklassen definiert,
die einen Index mit einem gespeicherten Wert bzw. Index-Ausdruck initialisieren.
Listing 12.6: ET-Programmierung der Schleifeninitialisierung.
template <c l a s s A>
s t r u c t Loop I n i t ope ra to r const A& () const ; ;
template <c l a s s A, c l a s s B>
c l a s s I n i t C : pub l i c Loop In i t <I n i tC <A,B> >
A& a ; const B b ;
pub l i c :
I n i t C (A& a , const B& b ) : a ( a ) , b ( b )
vo id i n i t ( ) const a . i t e r ( ) = b ;
;
template <c l a s s A, c l a s s B>
c l a s s I n i t I : pub l i c Loop In i t < I n i t I <A,B> > . . .
vo id i n i t ( ) const a . i t e r ( ) = b . i t e r ( ) ;
;
Dabei ist der Index (Membervariable A& a) nicht als konstant deklariert, da dieser ja im Laufe der In-
itialisierung verandert wird. Die zugehorigen Operatoren zum Erstellen der Initialisierungsobjekte sind
in der Klasse Index implementiert (Listing 12.9). Die in den Initalisierungsklassen definierte Methode
init , fuhrt die jeweilige Initialisierung durch.
Listing 12.7: ET-Code zur Durchfuhrung von Bedingungsabfragen.
template <c l a s s A>
s t r u c t LoopCond i t i on ope ra to r const A& () const ; ;
template <c l a s s A, c l a s s B>
c l a s s LessThanC : pub l i c LoopCondi t ion <LessThanC<A,B> >
const A& a ; B b ;
pub l i c :
LessThanC ( const A& a , const B& b ) : a ( a ) , b ( b )
boo l i sT rue ( ) const r e t u r n a . i t e r ( ) < b ;
;
template <c l a s s A, c l a s s B>
c l a s s LessThanI : pub l i c LoopCondi t ion <LessThanI<A,B> > . . .
boo l i sT rue ( ) const r e t u r n a . i t e r ( ) < b . i t e r ( ) ;
;
template <c l a s s A>
i n l i n e LessThanC<A, i n t > ope ra to r < ( const IndexEpr<A>& a , i n t i ) ;
template <c l a s s A, c l a s s B>
i n l i n e LessThanI<A,B> ope ra to r < ( const IndexEpr<A>& a , IndexEpr<B>& b ) ;
110
KAPITEL 12. WEITERE EINSATZGEBIETE VON ET
Die Kapselung der Bedingungsabfragen durch ET-Konstrukte wird ahnlich implementiert (Listing
12.7). Hierbei werden uber die Wrapper-Klasse LoopCondition entsprechende Vergleichsklassen eingefuhrt.
Diese testen in der Methode isTrue den in der Testklasse gespeicherten Ausdruck mit einer Konstanten
bzw. einem Index-Ausdruck.
Zuletzt benotigt man eine ET-Implementierung zur Erhohung bzw. Erniedrigung der Iterationsindi-
zes. Die in Listing 12.8 definierten Stride -Klassen erhohen einen Index um 1 bzw. um einen konstanten
Wert. Dieses Inkrementieren wird durch Aufruf der Methode stride durchgefuhrt.
Listing 12.8: ET-Implementierung fur die Durchfuhrung von Iterationsschritten.
template <c l a s s A>
s t r u c t LoopS t r i d e ope ra to r const A& () const ; ;
template <c l a s s A>
c l a s s P l u sP l u s : pub l i c LoopSt r i de <PlusP lus<A> >
A& a ;
pub l i c :
P l u sP l u s (A& a ) : a ( a )
vo id s t r i d e ( ) const ++(a . i t e r ( ) ) ;
;
template <c l a s s A, c l a s s B>
c l a s s PlusC : pub l i c LoopSt r i de<PlusC<A,B> >
A& a ; B b ;
pub l i c :
PlusC (A& a , const B& b ) : a ( a ) , b ( b )
vo id s t r i d e ( ) const ( a . i t e r ())+=b ;
;
Nach diesen ET-Implementierungen zur Darstellung der for-Schleifen wird in Listing 12.9 schließlich
die Index-Klasse prasentiert. Diese Index-Implementierung ist auf Integer-Werte als Indizes ausgerichtet,
prinzipiell konnen damit aber auch beliebige Index-Typen realisiert werden. Diese mussen lediglich
entsprechende Vergleichs- und Rechenoperatoren besitzen. Die zwei Versionen der Methode iter regeln
den Zugriff auf den eigentlichen Indexwer. Die Variante, die eine Referenz auf den internen iterator
zuruckgibt, wird fur Initialisierung und In- bzw. Dekrementierung benotigt, weil dabei der Indexwert
verandert wird. Die Methoden fur die Zuweisung und die In-/Dekrement-Operatoren sind in der Klasse
Index definiert, da nur an Indizes Werte zugewiesen werden konnen.
Listing 12.9: Index-Klasse fur die Schleifeniteration.
c l a s s I ndex : pub l i c IndexEpr<Index >
i n t i t e r a t o r ;
pub l i c :
I ndex ( i n t i = 0) : i t e r a t o r ( i )
i n t& i t e r ( ) r e t u r n i t e r a t o r ;
const i n t& i t e r ( ) const r e t u r n i t e r a t o r ;
. . .
I n i tC <Index , i n t > ope ra to r = ( i n t i )
i t e r a t o r = i ; r e t u r n I n i tC <Index , i n t >(∗ t h i s , i ) ;
I n i t I <Index , Index> ope ra to r = ( const I ndex& i )
i t e r a t o r = i . i t e r ( ) ; r e t u r n I n i t I <Index , Index >(∗ t h i s , i ) ;
template <c l a s s A>
I n i t I <Index , A> ope ra to r = ( const IndexEpr<A>& i )
r e t u r n I n i t I <Index ,A>(∗ t h i s , i ) ;
PlusP lus<Index> ope ra to r ++ ( i n t ) r e t u r n PlusP lus<Index >(∗ t h i s ) ;
PlusC<Index , i n t > ope ra to r += ( i n t b ) ;
;
Neben diesen ET-Implementierungen zur Initialisierung von Indizes, zum Test von Indexausdrucken
111
12.2. ITERATIONSSCHLEIFEN MITTELS ET
bzw. zur In-/Dekrementierung von Indizes, konnen auf die gleiche Weise noch erganzende Operationen
eingefuhrt werden. Zusatzlich sind auch Summationen, Subtraktionen, etc. von Indizes definierbar, so
dass das Spektrum der Indexberechnungen abgedeckt werden kann.
Unter Verwendung dieser ET-Programmierungen werden nun die Klassen zur Definition der Schleifen
eingefuhrt. Dabei treten mehrere Arten von Schleifen-Typen auf, die durch den Enumerationswert
LoopLayer angegeben werden.
enum LoopLayer Outer , I nne r , B locked ;
Zur allgemeinen Kapselung der verschiedenen Typen der Iterationsschleifen enthalt Listing 12.10
mit Loop Wrapper eine Wrapper-Klasse, welche die vorher definierten Teile einer Schleife beinhaltet.
Zusatzlich sind Methoden zur Durchfuhrung der Initialisierung ( init ), des Tests ( isTrue) und der In-
/Dekrementierung ( stride ) erklart. Diese rufen die Funktionen der gespeicherten Schleifeninhalte auf.
Listing 12.10: Wrapper-Klasse der ET-Iterationsschleifen.
template<c l a s s A, c l a s s B, c l a s s C, LoopLayer l a y e r > c l a s s IndexLoop ;
template<c l a s s A> c l a s s Loop Wrapper ;
template<c l a s s A, c l a s s B, c l a s s C, LoopLayer l a y e r >
c l a s s Loop Wrapper <IndexLoop<A,B,C , l a y e r > >
t ypede f IndexLoop <A,B,C , l a y e r > LOOP;
const A& a ; const B& b ; const C& c ;
pub l i c :
Loop Wrapper ( const A& a , const B& b , const C& c ) : a ( a ) , b ( b ) , c ( c )
ope ra to r const LOOP&() const r e t u r n ∗ s t a t i c c a s t <const LOOP∗>( t h i s ) ;
vo id i n i t ( ) const a . i n i t ( ) ;
boo l i sT rue ( ) const r e t u r n b . i sT rue ( ) ;
vo id s t r i d e ( ) const c . s t r i d e ( ) ;
;
In der abschließenden Definition der Klasse IndexLoop fugen sich die vorher eingefuhrten ET-Imple-
mentierungen zusammen. Sie stellt die Schleife dar, in der die eigentliche Iteration initiiert wird. Um
eine Notation ahnlich der for-Schleife zu erhalten, wird ein entsprechendes ET-Objekt uber die Creator-
Funktion for loop erstellt, die die Schleifen-Definition als Parameter besitzt. Die Iteration wird dann
im Klammeroperator (operator()) des ET-Schleifen-Objekts durchgefuhrt. Dabei werden die gespeicher-
ten Schleifenbestandteile in eine herkommliche for-Schleife eingesetzt. Der Klammeroperator hat als
Argument ein Evaluate-Objekt, woruber die Berechnungen ausgefuhrt werden.
Listing 12.11: ET-Iterationsschleife zur Durchfuhrung einfacher Iterationen.
template<c l a s s A, c l a s s B, c l a s s C>
s t r u c t IndexLoop <A,B,C , Outer> : pub l i c Loop Wrapper<IndexLoop <A,B,C , Outer> >
t ypede f IndexLoop <A,B, C , Outer> LOOP;
IndexLoop ( const A& a , const B& b , const C& c ) ;
template <c l a s s D, c l a s s E>
vo id ope ra to r ( ) ( const Eva lua te<D,E>& ev ) const
f o r ( t h i s−> i n i t ( ) ; t h i s−>i sT rue ( ) ; t h i s−>s t r i d e ( ) )
ev . i t e r a t i o n ( ) ;
;
template <c l a s s A, c l a s s B, c l a s s C>
i n l i n e IndexLoop <A,B,C , Outer>
f o r l o o p ( const Loop In i t <A>& a , const LoopCondi t ion <B>& b , const LoopSt r i de <C>& c ) ;
Das nachfolgende Listing 12.12 prasentiert die Anwendung der so realisierten ET-Schleife. Dabei
wird eine zusatzlich definierte skalare Multiplikation von Indizes mit Vektoren verwendet.
112
KAPITEL 12. WEITERE EINSATZGEBIETE VON ET
Listing 12.12: Anwendungsbeispiel der ET-Schleifen.
I ndex J ;
f o r l o o p ( J = 0 , J < i t e r , J++)
(
b == d + 2.∗ a + J∗c
) ;
Dieser eingefuhrte Ansatz, die Iterationsschleifen in ET-Objekten zu kapseln, erweist sich als sehr
allgemein. Beispielsweise konnen durch einfache Erweiterungen auch geschachtelte Schleifen behandelt
werden.
Listing 12.13: ET-Programmierung von geschachtelten Schleifen.
template<c l a s s A, c l a s s B, c l a s s C>
s t r u c t IndexLoop <A,B,C , I nne r > :
pub l i c Loop Wrapper<IndexLoop <A,B,C , I nne r > >
t ypede f IndexLoop <A,B,C , I nne r > LOOP;
IndexLoop ( const A& a , const B& b , const C& c )
: Loop Wrapper<LOOP> ( a , b , c )
template <c l a s s D, c l a s s E>
Eva lua te<LOOP, Eva lua te<D,E> >
ope ra to r ( ) ( const Eva lua te<D,E>& ev ) const
r e t u r n Eva lua te<LOOP, Eva lua te<D,E> >(∗ t h i s , ev ) ;
;
template <c l a s s A, c l a s s B, c l a s s C>
i n l i n e IndexLoop<A,B,C , I nne r >
f o r ( const Loop In i t <A>& a , const LoopCondi t ion <B>& b , const LoopSt r i de<C>& c )
r e t u r n IndexLoop <A,B,C , I nne r >(a , b , c ) ;
Dazu wird in Listing 12.13 die ET-Implementierung einer inneren Schleife aufgefuhrt. Im Grunde
unterscheidet sich diese nur durch den anders definierten Klammeroperator von der außeren Schlei-
fenklasse. Fur innere Schleifen erzeugt der Klammeroperator ein weiteres Evaluate-Objekt, welches die
Schleife selbst und den Schleifenrumpf speichert. D.h., die Ausfuhrung der inneren Schleife wird wie-
derum verzogert. Die Creator-Funktion for erzeugt das entsprechende IndexLoop-Objekt.
In Erganzung zu Listing 12.13 zeigt das nachfolgende Listing 12.14 die Spezialisierung der Evaluate-
Klasse, die fur den Fall definiert ist, bei dem die erste Komponente eine innere ET-Schleife ist. Dabei
wird in der Methode iteration die for-Schleife mit den Parametern der gespeicherten Schleife ausgefuhrt.
Mit dieser Spezialisierung der Evaluate-Klasse konnen beliebig viele ET-Schleifen geschachtelt werden.
Die Iterationen werden dann sukzessive uber die außerste Schleife ausgefuhrt.
Listing 12.14: ET-Klasse zur Kapselung der inneren Schleifen.
template<c l a s s A, c l a s s B, c l a s s C , c l a s s E>
c l a s s Eva lua te<IndexLoop<A,B,C , I nne r >,E>
t ypede f IndexLoop <A,B,C , I nne r > LOOP;
const LOOP& loop ;
const E& ev ;
pub l i c :
E v a l ua t e ( const LOOP& l , const E& ev ) : l oop ( l ) , ev ( ev )
vo id i t e r a t i o n ( ) const
f o r ( l oop . i n i t ( ) ; l oop . i sT rue ( ) ; l oop . s t r i d e ( ) )
ev . i t e r a t i o n ( ) ;
;
Listing 12.15 zeigt die Anwendung der geschachtelten ET-Schleifenaufrufe. Dabei wird mit der
Creator-Funktion for ein inneres Schleifenobjekt erzeugt.
113
12.3. ET-SCHLEIFENBLOCKING MIT NUMMERIERTEN VARIABLEN
Listing 12.15: Geschachtelte Anwendung der ET-Schleifen.
I ndex J , K;
f o r l o o p ( J = 0 , J < i t e r , J++)
(
f o r (K = J , K < i t e r , K++)
(
d == b + J∗a + K∗ c
)
) ;
Mit den prasentierten Implementierungen lassen sich Iterationsschleifen in ET-Ausdrucke umwan-
deln. Die resultierenden ET-Objekte werden beim Ubersetzen optimiert, so dass die resultierenden
Programme die gleiche Leistung wie Implementierungen mit herkommlichen for-Schleifen erbringen.
In der jetzigen Form fuhren die ET-Schleifen die Iterationen in der gleichen Reihenfolge wie die ei-
gentlichen for-Schleifen durch. Doch kann aufbauend auf dieser Implementierung ein Schleifenblocking
realisiert werden.
12.3 ET-Schleifenblocking mit nummerierten Variablen
Da die numerische Simulation realistischer Problemstellungen in großen Gleichungssystemen mundet,
werden effiziente Losungsalgorithmen benotigt, um annehmbare Berechnungszeiten zu erhalten. Eine
Moglichkeit die Laufzeiten der Algorithmen zu verkurzen, besteht in der Erhohung der Speichereffizi-
enz [Kow04]. Dabei wird versucht, die Datenlokalitat im Cache zu verbessern, indem der verwendete
Speicher in Blocke aufgeteilt wird, die in den Cache passen. Vor allem bei den schwach besetzten
Matrizen, die aus der Diskretisierung partieller Differentialgleichungen resultieren, ist dies sinnvoll.
Dabei verknupfen die diskretisierten Differentialoperatoren nur geometrisch benachbarte Punkte, so
dass zur einer Berechnung eines Eintrags lediglich geringe Datenmengen benotigt werden. Die Große
der behandelbaren Blocke hangt dabei von der Cache-Große, sowie von der Anzahl der Variablen im
zu berechnenden Ausdruck ab.
Da durch die ET-Implementierungen Ausdrucksbaume erzeugt werden, kann aus diesen bestimmt
werden, wie viele verschiedene Variablen im jeweiligen Ausdruck vorkommen. Dies kann mit herkomm-
lichen Vektor-Klassen realisiert werden, indem bei jedem Konstruktoraufruf ein neuer Index vergeben
wird. Dieser wird intern gespeichert, so dass damit mittels Durchlauf durch die Ausdrucksbaume die
Anzahlen der darin verschiedenen Vektoren bestimmt werden konnen.
Nachfolgende Programmierung basiert jedoch auf den template-nummerierten Vektoren der FET-
Variante (Listing 6.1), da hier ein solcher Index bereits in den Typen der Vektoren gegeben ist. Dadurch
sind die Informationen uber die verschiedenen Vektoren in einem Ausdruck bereits zur Ubersetzungszeit
bekannt. Um die Anzahl der unterschiedlichen Vektoren auch bereits beim Compilieren bestimmen zu
konnen, werden die Vektoren der Ausdrucke in sog. type lists gespeichert [Ale01]. Eine entsprechende
Implementierung findet sich in Anhang C. Von einer solchen Typliste kann dann nach dem Loschen
der Duplikate die Lange bestimmt werden, welche die Anzahl der verschiedenen Variablen angibt.
Als Beispiel fur die erforderlichen Erweiterungen zeigt das nachfolgende Listing 12.16 die Definition
des Typs VARIABLE LIST in der Additionsklasse. Dabei werden die Typlisten der beiden Operanden
zusammengefasst. Passend dazu wird in der Klasse Vector (Listing 12.17) eine Typliste initialisiert.
Listing 12.16: Typlist-Definition in der Additionsklasse.
template <c l a s s A, c l a s s B>
c l a s s Add : pub l i c Expr<Add<A,B> > . . .
t ypede f typename Jo in<typename A : : VARIABLE LIST ,
typename B : : VARIABLE LIST > : : LIST VARIABLE LIST ;
;
114
KAPITEL 12. WEITERE EINSATZGEBIETE VON ET
Um die Auswertung von Vektorausdrucken in Blocken durchfuhren zu konnen, wird in der Klasse
Vector zusatzlich eine Methode evaluateBlock definiert. Diese wertet den ubergebenen Ausdruck nur vom
Index start bis end aus.
Listing 12.17: Erweiterung der Vektorklasse zur Durchfuhrung des Blockings.
template < i n t un ique I d >
c l a s s Vector : pub l i c Expr<Vector <un ique I d > > . . .
t ypede f TYPELIST ( Vector <num>) VARIABLE LIST ;
template<c l a s s A>
vo id e v a l ua t eB l o ck ( const Expr<A>& a , i n t s t a r t , i n t end )
const A& a ( a ) ;
f o r ( i n t i = s t a r t ; i < end ; ++i )
da ta [ i ] = a . g i v e ( i ) ;
;
Dem entsprechend wird in der Klasse Evaluate eine Methode zur Iteration uber die Blocke eingefuhrt
( iteration in Listing 12.18). Die Funktion size gibt die Große des Ergebnisvektors zuruck.
Listing 12.18: Erweiterung der Evaluate-Klasse fur Schleifenblocking.
template <c l a s s A, c l a s s B>
c l a s s Eva l ua t e
A& a ; const B& b ;
. . .
i n t s i z e ( ) const r e t u r n a . s i z e ( ) ;
vo id i t e r a t i o n ( i n t s t a r t , i n t end ) const
a . e v a l ua t eB l o ck (b , blocknumber , b l o c k s i z e ) ;
;
Schließlich zeigt Listing 12.19 die Implementierung der geblockten ET-Schleife. In der Methode
operator() wird in Zeile 9 die Anzahl der unterschiedlichen Vektorvariablen bestimmt. Daraus werden in
den Zeilen 10 und 11 uber die Lange der Vektoren und die Cachegroße ( CACHESIZE ) die Anzahl und
Große der Schleifenblocke bestimmt. In der darauf folgenden for-Schleife werden die Blocke abgearbei-
tet, und abschließend der letzte Block, der im Allgemeinen kleiner ist als die vorherigen Blocke.
Listing 12.19: ET-Iterationsschleife zur Durchfuhrung des Blockings.
template<c l a s s A, c l a s s B, c l a s s C>
s t r u c t IndexLoop <A,B,C , Blocked> :
pub l i c Loop Wrapper<IndexLoop <A,B,C , Blocked> >
t ypede f IndexLoop <A,B, C , Blocked> LOOP;
5 IndexLoop ( const A& a , const B& b , const C& c ) : Loop Wrapper<LOOP>(a , b , c )
template <c l a s s D, c l a s s E>
vo id ope ra to r ( ) ( const Eva lua te<D,E>& ev ) const
i n t i =0, n rVa r s=CountVar i ab l e s <D,E> : : v a l ue ,
n rB l o ck s=(ev . s i z e ( )∗ nrVa r s )/ CACHESIZE +1, b l o c k s i z e=ev . s i z e ( )/ n rB l o ck s ;
10 f o r ( i = 0 ; i < n rB l o ck s ; ++i )
i n t s = i ∗ b l o c k s i z e , end = s+b l o c k s i z e ;
f o r ( t h i s−> i n i t ( ) ; t h i s−>i sT rue ( ) ; t h i s−>s t r i d e ( ) )
ev . i t e r a t i o n ( s , end ) ;
15 f o r ( t h i s−> i n i t ( ) ; t h i s−>i sT rue ( ) ; t h i s−> s t r i d e ( ) )
ev . i t e r a t i o n ( i ∗ b l o c k s i z e , ev . s i z e ( ) ) ;
;
template <c l a s s A, c l a s s B, c l a s s C2>
20 i n l i n e IndexLoop<A,B,C , Blocked>
f o r b l o c k e d ( const Loop In i t <A>& a , const LoopCondi t ion <B>& b , const LoopSt r i de<C>& c ) ;
115
12.3. ET-SCHLEIFENBLOCKING MIT NUMMERIERTEN VARIABLEN
Die in Listing 12.19 prasentierte Version des Schleifenblockings verwendet keine Uberlappung der
Schleifen. Die Creator-Funktion for blocked erzeugt die geblockte ET-Schleife.
Das mit dieser Implementierung realisierte Schleifenblocking kann wie die vorherigen Schleifen im
Anwendercode verwendet werden. Beispielsweise zeigt Listing 12.20 die Berechnung des Gauß-Seidel-
Losers unter der geblockten Schleifeniteration.
Listing 12.20: Anwendercode des geblockten Gauß-Seidel-Losers.
I ndex J ;
f o r b l o c k e d ( J = 0 , J < i t e r , J++)
(
u == 0.25 ∗ ( 1 . / ( h∗h ) ∗ b + N(u ) + S(u ) + W(u ) + E(u ) ) ;
) ;
Die prasentierten ET-Implementierungen lassen sich auch auf while-Schleifen und allgemeinere Be-
dingungen erweitern. Dadurch konnen in den Bedingungsabfragen nicht nur Indizes verwendet werden,
sondern z.B. auch eine Vektornorm. Dies eroffnet ein breites Anwendungsfeld einer solchen Kapselung
von Schleifen in ET-Ausdrucken.
Abschließend zeigt Abbildung 12.1 die Effizienz der erhaltenen ET-Blocking-Programmierungen.
Darin werden ein C-Code ohne Blocking, eine C-Version mit Schleifenblocking sowie die prasentier-
te ET-Schleifenblocking-Implementierung verglichen. Als Anwendung dient der Gauß-Seidel-Loser fur
das zweidimensionale Poisson-Problem, durchgefuhrt auf einer Pentium 4 Workstation (Anhang D.1.1),
ubersetzt mit dem GNU-Compiler (Version 4.1.0). Zum Vergleich der Resultate werden hierzu nicht
die Zeiten pro Iteration betrachtet, sondern die Ausfuhrungszeiten die benotigt werden, um ein Ergeb-
nis mit vorgegebener Genauigkeit zu erhalten. Wahrend die Implementierung ohne Blocking deutlich
schlechter wird, sobald die gemeinsamen Datenmengen der Vektoren die Cache-Große ubersteigt, be-
sitzen die anderen Versionen ein gleichbleibendes Performance-Verhalten.
Abbildung 12.1: Performance-Vergleich des Gauß-Seidel-Losers implementiert in C-Code ohne
Blocking, C-Code mit Blocking und ET-Schleifen-Blocking.
116
KAPITEL 13. ABSCHLIESSENDE BEMERKUNGEN
13 Resumee und abschließende Bemerkungen
Die ET-Implementierungstechnik beschreibt eine interessante und vielseitig einsetzbare Methode zur
Realisierung effizienter Programme. Die Darstellung von Ausdrucksbaumen mittels templatisierter
Klassen ermoglicht den C++-Compiler weitreichende Optimierungsmoglichkeiten.
13.1 Zusammenfassung
Die Ergebnisse der vorliegenden Arbeit beinhalten gundlegende Weiterentwicklungen der ET-Technik
selbst, und zeigen den anwendungsbezogenen Einsatz der erarbeiteten Implementierungsstrategien zur
numerischen Losung partieller Differentialgleichungen.
13.1.1 Einfache und Leistungsstarke ET-Implementierungen
Aufbauend auf den grundlegenden Untersuchungen in Kapitel 4 konnten ET-Implementierungen pra-
sentiert werden, die einfach und kurz zu realisieren sind (Kapitel 5). Dies war einerseits eine ET-
Version fur lokal benutzte ET-Objekte, sowie eine Variante zum Abspeichern und Wiederverwenden
von ET-Ausdrucken. Diese strukturierte Betrachtung und Darstellung der ET-Technik ermoglichte die
vielfaltigen Anwendungen in Teil III der vorliegenden Arbeit. Gerade die vereinfachte Programmierung
und die genaue Analyse der Gultigkeitsdauer der ET-Objekte vermeiden die haufigsten Fehlerquellen
wahrend der Software-Entwicklung.
Zusatzlich werden mit den weiterentwickelten FET-Techniken die bestehenden Performance-Proble-
me der ET-Varianten gelost. Durch die eindeutige Template-Nummerierung der Vektorvariablen wer-
den die Compiler bei der Durchfuhrung der Optimierungen entscheidend unterstutzt. Somit erreichen
FET-Implementierungen auch auf Hoch- und Hochstleistungsrechnern die Peformance vergleichbarer
Programme im C-Stil. Dies ermoglicht ET-Anwendungen, die auf Arbeitsplatzrechnern entwickelt wur-
den, nahezu identisch auf Hochleistungsrechner zu ubertragen, und dennoch die plattformspezifische
Rechenleistung voll nutzen zu konnen. Gerade mit der Zielsetzung die ET-Technik zur numerischen
Losung partieller Differentialgleichungen einzusetzen, ist dies eine entscheidende Verbesserung zu den
herkommlichen ET-Varianten. Denn die aus der Diskretisierung von realistischen Problemstellungen
resultierenden großen Gleichungssysteme machen den Einsatz von Hochleistungsplattformen oft un-
umganglich.
13.1.2 ET zum Losen von partiellen Differentialgleichungen
Die appoximative Berechnung partieller Differentialgleichungen basiert auf der Implementierung vie-
ler komplexer Datenstrukturen und Rechenoperationen. Dies erstreckt sich von der Erstellung der
Diskretisierungsgitter bis zur Anwendung entsprechender Losungsalgorithmen fur die resultierenden
Gleichungssysteme. Wegen der zusatzlichen Forderung nach effizienten Implementierungen bietet sich
zur Realisierung solcher Bibliotheken C++, als Sprache mit programmiersprachlichen Mitteln zur mo-
dularen und effizienten Programmierung, an. Daruber hinaus ermoglicht C++ mit dem Uberladen
von Operatoren eine benutzerfreundliche und intuitive Realisierung mathematischer Problemstellun-
gen. Dadurch konnen die teilweise komplexen Berechnungsvorgange, die sich hinter den Operationen
stecken, vor den Anwendern verborgen werden.
117
13.2. WEITERE ENTWICKLUNGEN UND AUSBLICK
Die effiziente Implementierung der uberladenen mathematischen Operatoren kann mit ET erreicht
werden. Die dabei generierten Ausdrucksbaume erlauben auf Grund der durchgangigen Typinformation
ein vollstandiges Inlining der Funktionsaufrufe bei der Auswertung. Somit resultieren leistungsstarke
Programme, die auch fur komplizierte Datenstrukturen und Formeln performante Losungen bieten.
Die Template-Bibliothek Colsamm (Kapitel 11) demonstrierte dazu, wie durch die Kombination
mehrerer ET- bzw. FET-Implementierungen auch abstrakte Objekte wie Finite Elemente sehr ma-
thematisch modelliert werden konnen. Mittels einer durchdachten Verknupfung von Template- und
ET-Techniken mit der traditionellen Datenkapselungen sind somit effiziente und flexible Bibliotheken
realisierbar. Aufbauend auf dem strukturierten Programmablauf von Colsamm, der unnotige Berech-
nungen verhindert, konnten die notwendigen Funktionalitatseinheiten herausgearbeitet und modular
programmiert werden. Diese und die zugrunde liegenden mathematischen Formulierungen lieferten
konkrete Ansatze fur den Einsatz der ET-Implementierungen. Dabei ermoglichten die weitreichen-
den Untersuchungen und Verbesserungen der ET-Technik im Teil II dieser Arbeit eine unmittelbare,
problemnahe Realisierung der mathematischen Schnittstellen.
Zusatzlich ergeben sich aus den prasentierten ET- und FET-Techniken weiterfuhrende Anwen-
dungsmoglichkeiten, die uber die Darstellung mathematischer Ausdrucksbaume und Terme hinaus-
gehen. Dies zeigte beispielsweise die Kapselung von for-Schleifen in ET-Ausdrucken in Abschnitt 12.2.
Dabei wurden durch den Einsatz von mehreren ET-Implementierungen die Bestandteile von Iterati-
onsschleifen in gesonderte ET-Objekte umgewandelt, um so schleifenubergreifende Optimierungen und
leistungsverbessernde Schleifentransformationen explizit durchfuhren zu konnen.
13.2 Weitere Entwicklungen und Ausblick
Aus den Ergebnissen der vorliegenden Arbeit eroffnen sich weite Anwendungsmoglichkeiten der ET-
Technik zum Losen von partiellen Differentialgleichungen. Prinzipiell bietet nahezu jede Abstraktions-
stufe in PDG-Bibliotheken einen Ansatzpunkt fur den Einsatz entsprechender ET-Mechanismen.
Die ET-Implementierungen eignen sich zur effizienten Realisierung der mathematischen Operatio-
nen. Dies war Ziel des Ansatzes von Veldhuizen, und spiegelt auch die große Menge der Einsatzgebiete
von ET wider. Dabei besteht die Leistung der ET-Technik in der Kapselung der Ausdrucke als Typ-
informationen, was den C++-Compilern umfassende Optimierungsmoglichkeiten bietet. Doch konnen
ET nicht nur fur offensichtliche Ansatze wie die Addition, Subtraktion, etc. von (mathematischen)
Objekten eingesetzt werden. Denkbar sind auch Implementierungen, welche die Operatoren bzgl. der
problemspezifischen Anwendung mit komplett neuer Bedeutung definieren. So konnen beispielsweise
die logischen Operatoren & und | verwendet werden, um Vektoren oder Matrizen sehr intuitiv pro-
grammieren zu konnen.
Prinzipiell ausbaufahiger ist jedoch eine Verwendung von ET, die uber die Grenzen der semantischen
Ausdrucke hinaus geht. Dies umfasst einerseits das abspeichern von ET-Objekten (Abschnitt 7.3). In
den meisten bestehenden Bibliotheken und Anwendungen werden die ET-Ausdrucke nur direkt bei
ihrer Erstellung verwendet. Doch durch eine entsprechende Speicherung konnen die ET-Objekte auch
an nachfolgenden Stellen im Programm mit gleichbleibenden Optimierungsmoglichkeiten genutzt wer-
den. Naturlich nur, wenn die ET-Ausdrucke mit ihrer vollen Typinformation ohne abstrakte Klassen
abgespeichert sind. Daraus eroffnet sich unmittelbar ein Einsatz von ET zur Programmierung von
wiederverwendbaren Formeln, die wiederholt fur wechselnde Daten ausgewertet werden mussen. Dies
wurde beispielsweise bei der Realisierung der Template-Bibliothek Colsamm zur effizienten Implemen-
tierung der Transformationsformeln verwendet.
Die großten und vielfaltigsten Entwicklungsmoglichkeiten bestehen jedoch beim Einsatz von ET-
Implementierungen zur erweiterten Optimierung ganzer Anweisungsblocke. Dies zeigte beispielsweise
die Kapselung der for-Schleifen mittels ET-Objekten zur automatischen Durchfuhrung von benutzer-
gesteuerten Optimierungen (behandelt in Abschnitt 12.2). Dabei kann die eigentliche programmier-
118
KAPITEL 13. ABSCHLIESSENDE BEMERKUNGEN
sprachliche Syntax und Semantik von C++ zwar nicht exakt, aber sehr ahnlich imitiert werden. Dieser
Ansatz kann entsprechend auch fur weitere Programmeinheiten verwendet werden, in denen durch
Kapselung ausdrucksubergreifender Informationen weiterfuhrende Transformationen des eigentlichen
Programmcodes moglich sind bzw. zusatzliche Optimierungen durchgefuhrt werden konnen. Dadurch
sind grundsatzlich Implementierungen von domain specific embedded languages (DSEL) denkbar – und
C++-Bibliotheken zur numerischen Losung partieller Differentialgleichungen sind prinzipiell DSELs
– deren Anwendungen aus mehreren ET-Objekten bestehen, die bestimmte Programmeinheiten kap-
seln. Diese erlauben dann die explizite Durchfuhrung von DSEL-spezifischen Optimierungen, welche
die C++-Compiler bei herkommlichen Implementierungen selbst nicht erreichen konnen.
Somit eroffnet die ET-Technik gerade fur das wissenschaftliche Rechnen eine Vielzahl von Einsatzge-
bieten. Den Entwicklern stehen leistungsstarke Mechanismen zur Realisierung der jeweilig passenden,
benutzerfreundlichen sowie effizienten DSEL zur Verfugung, die auf Grund der Ergebnisse der vorlie-
genden Arbeit einfach und intuitiv implementiert werden konnen.
119
13.2. WEITERE ENTWICKLUNGEN UND AUSBLICK
120
Teil IV
Anhang
ANHANG A. ET-IMPLEMENTIERUNG NACH VELDHUIZEN
A ET-Implementierung nach Veldhuizen
Die Einfuhrung der ET-Implementierungstechnik von Veldhuizen in [Vel95] revolutionierte das Ope-
ratoruberladen in C++. Um einen Eindruck der Entwicklungen in der vorliegenden Arbeit zu bieten,
wird in diesem Kapitel die erste von Veldhuizen veroffentlichte Implementierungsvariante erortert. Die
nachstehenden Programmcodes sind aus [Vel98] entnommen.
Listing A.1: Vektor-Klasse der ET-Implementierung nach Veldhuizen.
c l a s s DVec ;
s t r u c t DVecAss ignable
v i r t u a l vo id a s s i g n (DVec&) const = 0 ;
;
c l a s s DVec
p r i v a t e :
double ∗ data ;
i n t l e n g t h ;
pub l i c :
t ypede f double ∗ i t e rT ;
t ypede f double elementT ;
DVec( i n t n ) : l e n g t h ( n ) data = new double [ n ] ;
˜DVec ( ) de l e t e [ ] da ta ;
double& ope ra to r [ ] ( i n t i ) r e t u r n data [ i ] ;
i t e rT beg in ( ) const r e t u r n data ;
i t e rT end ( ) const r e t u r n data + l e n g t h ;
i n t l e ng th ( ) const r e t u r n l e n g t h ;
DVec& ope ra to r=(const DVecAss ignable& x )
x . a s s i g n (∗ t h i s ) ; r e t u r n ∗ t h i s ;
;
Dazu prasentiert das Listing A.1 die Implementierung der Vektor-Klasse (hier DVec). Im damaligen
C++-Standard waren noch keine Member-Templates zugelassen, d.h. die Definition von templati-
sierten Membermethoden war nicht moglich. Aus diesem Grund wurde bei der Programmierung des
Zuweisungsoperators uber die abstrakte Klasse DVecAssignable eine Schnittstelle fur die ET-Ausdrucke
geschaffen. Der dabei auftretende virtuelle Funktionsaufruf bei der Auswertung tritt nur in der außers-
ten Schicht eines Ausdrucks auf. Die restlichen Funktionsaufrufe konnen mittels Inlining optimiert
werden.
Um in den ET-Ausdrucken keine Vektorobjekte kopieren zu mussen, werden nur die Datenzeiger
abgespeichert. Aus diesem Grund sind die Funktionen begin und end sowie der Typ iterT definiert.
Die Funktion assignResult in Listing A.2 beschreibt die globale templatisierte Auswertungs- und Zu-
weisungsfunktion. Dabei sind uber Makros unterschiedliche Auswertungsvarianten fur verschiedene
Plattformen definiert. Zusatzlich zeigt Listing A.2 die Implementierung der Wrapper-Klasse (DVExpr).
Dabei werden im Wrapper volle Objekte abgespeichert, die somit beim Aufbau mehrfach kopiert wer-
den mussen. Weiter definiert die Klasse DVExpr als Ableitung der abstrakten Klasse DVecAssignable die
virtuelle assign-Methode. Diese ruft die globale Funktion assignResult auf.
123
ANHANG A. ET-IMPLEMENTIERUNG NACH VELDHUIZEN
Listing A.2: Funktion zur Auswertung und Zuweisung von ET-Objekten.
template<c l a s s I t e r >
vo id a s s i g nR e s u l t (DVec& a , const I t e r& r e s u l t )
#i f d e f EXPRVEC USE TEMPORARY EXPRESSION
// Make a temporary copy o f the i t e r a t o r . Thi s i s f a s t e r on segmented
// a r c h i t e c t u r e s , s i n c e a l l the i t e r a t o r s a r e i n the same segment .
I t e r r e s u l t 2 = r e s u l t ;
#e l s e
// Otherwise , c a s t away cons t ( eek ! ) . No ha rmfu l s i d e e f f e c t s .
I t e r& r e s u l t 2 = ( I t e r &) r e s u l t ;
#e n d i f
#i f d e f EXPRVEC USE INDIRECTION
double ∗ i t e r = a . beg in ( ) ;
f o r ( r e g i s t e r i n t i = a . l e ng th ( ) ; i −−;)
i t e r [ i ] = r e s u l t 2 [ i ] ; // I n l i n e d e x p r e s s i o n
#e l s e
double ∗ i t e r = a . beg in ( ) ;
double ∗ end = a . end ( ) ;
do
∗ i t e r = ∗ r e s u l t 2 ; // I n l i n e d e x p r e s s i o n
++r e s u l t 2 ;
wh i l e (++ i t e r != end ) ;
#e n d i f
template<c l a s s A>
c l a s s DVExpr : pub l i c DVecAss ignable
A i t e r ;
pub l i c :
DVExpr ( const A& a ) : i t e r ( a )
double ope ra to r ∗ ( ) const r e t u r n ∗ i t e r ;
double ope ra to r [ ] ( i n t i ) const r e t u r n i t e r [ i ] ;
vo id ope ra to r++() ++i t e r ;
// V i r t u a l f u n c t i o n from DVecAss ignable . C a l l e d by
// DVec : : ope r a to r =(DVecAss ignable &).
v i r t u a l vo id a s s i g n (DVec& x ) const a s s i g nR e s u l t ( x , ∗ t h i s ) ;
;
Schließlich folgt die Programmierung der Additionklasse und der Operatoren. Die Addition wird als
Spezialfall der binaren Operationsklasse DVBinExprOp eingefuhrt. Diese Klasse speichert die Operanden
als volle Objekte. Die uberladenen Operatoren, die in vier Varianten zu definieren sind, erzeugen
daraus die entsprechenden Additionsobjekte. Im Falle von Vektoren als Operanden werden jedoch
die internen Pointer der Vektoren gespeichert. Somit wird das unnotige Kopieren der Vektor-Klasse
umgangen. Dabei ist jedoch zu bemerken, dass die Implementierung der Operatoren auf Grund der
geschachtelten Template-Konstrukte sehr komplex wird.
124
ANHANG A. ET-IMPLEMENTIERUNG NACH VELDHUIZEN
Listing A.3: Operationsklassen und Operatoren der ursprunglichen ET-Variante
s t r u c t DApAdd
s t a t i c i n l i n e double app l y ( double a , double b ) r e t u r n a+b ;
;
template<c l a s s A, c l a s s B, c l a s s Op>
c l a s s DVBinExprOp
A i t e r 1 ; B i t e r 2 ;
pub l i c :
DVBinExprOp( const A& a , const B& b) : i t e r 1 ( a ) , i t e r 2 (b )
vo id ope ra to r++() ++i t e r 1 ; ++i t e r 2 ;
double ope ra to r ∗ ( ) const r e t u r n Op : : app l y (∗ i t e r 1 ,∗ i t e r 2 ) ;
double ope ra to r [ ] ( i n t i ) const
r e t u r n Op : : app l y ( i t e r 1 [ i ] , i t e r 2 [ i ] ) ;
;
DVExpr<DVBinExprOp<double ∗ , double ∗ ,DApAdd> >
ope ra to r+(const DVec& a , const DVec& b )
t ypede f DVBinExprOp<double ∗ , double ∗ ,DApAdd> ExprT ;
r e t u r n DVExpr<ExprT>(ExprT ( a . beg in ( ) , b . beg in ( ) ) ) ;
template<c l a s s A>
DVExpr<DVBinExprOp<DVExpr<A>,double ∗ ,DApAdd> >
ope ra to r+(const DVExpr<A>& a , const DVec& b )
t ypede f DVBinExprOp<DVExpr<A>,double ∗ ,DApAdd> ExprT ;
r e t u r n DVExpr<ExprT>(ExprT ( a , b . beg in ( ) ) ) ;
template<c l a s s A>
DVExpr<DVBinExprOp<double ∗ ,DVExpr<A>,DApAdd> >
ope ra to r+(const DVec& a , const DVExpr<A>& b)
t ypede f DVBinExprOp<double ∗ ,DVExpr<A>,DApAdd> ExprT ;
r e t u r n DVExpr<ExprT>(ExprT ( a . beg in ( ) , b ) ) ;
template<c l a s s A, c l a s s B>
DVExpr<DVBinExprOp<DVExpr<A>,DVExpr<B>,DApAdd> >
ope ra to r+(const DVExpr<A>& a , const DVExpr<B>& b)
t ypede f DVBinExprOp<DVExpr<A>,DVExpr<B>,DApAdd> ExprT ;
r e t u r n DVExpr<ExprT>(ExprT ( a , b ) ) ;
125
ANHANG A. ET-IMPLEMENTIERUNG NACH VELDHUIZEN
126
ANHANG B. COLSAMM – PROGRAMMSTRUKTUR
B Colsamm – Programmstruktur
In diesem Kapitel werden einige grundlegende Implementierungsdetails zur Bibliothek Colsamm er-
lautert. Da die problemspezifischen Parameter der Finiten Elemente (siehe Abschnitt 11.1.1) zum Teil
unterschiedliche Berechnungsmechanismen zur Folge haben, wurde Colsamm als Template-Bibliothek
aufgebaut.
B.1 Template-Bibliotheken
Die Strategie bei der Entwicklung einer Template-Bibliothek besteht in der Kapselung moglichst vieler
Teile der objektspezifischen Parameter in Templates sowie spezialisierte Klassendeklarationen. Dadurch
werden zur Ubersetzungszeit die, von den gewahlten Template-Argumenten abhangigen, Instantiierun-
gen ausgewahlt, und konnen unter Einbeziehung aller Optimierungen compiliert werden. Somit entste-
hen keine uberflussigen Fallunterscheidungen zur Laufzeit. Des Weiteren werden nur die fur die aktu-
elle Anwendung benotigten Codeteile instantiiert und optimiert, nicht die gesamte Bibliothek. Diese
Template-Parameter und Spezialisierungen haben jedoch zu Folge, dass fur wechselnde Parameter neue,
entsprechend instantiierte Klassen verwendet werden mussen. Die Template-Parameter konnen nicht
dynamisch verandert werden (etwa uber ein entsprechendes Config-File). Bei einer Verwendung von
Template-Bibliotheken werden anstatt einer Erzeugung von Klassenobjekten entsprechende Template-
Instantiierungen generiert. Aus diesem Grund ist jedoch immer der gesamte Quellcode einzubinden,
und es konnen keine vorcompilierten Objektdateien erstellt werden.
Eine gangige Technik bei der Implementierung einer Template-Bibliothek ist die Verwendung der
template-gesteuerten Vererbung. Dabei werden die Template-Argumente der Basisklasse durch die
Template-Parameter der abgeleiteten Klasse definiert, siehe auch Abschnitt 2.3.5. Damit konnen bei
entsprechend angelegten Abhangigkeiten die fur die aktuelle Problemstellung notwendigen Klassen in
einer oder mehreren abgeleiteten Klassen zusammengefugt werden.
B.2 Programmstruktur
Aus der Definition von Finiten Elementen ergeben sich, erganzt durch PDG-spezifische Informationen
wie etwa Dimension, die Funktionseinheiten und Abhangigkeiten, die eine modulare Programmierung
einer flexiblen der FEM-Diskretisierung ermoglichen (siehe Abschnitt 11.1.1). Nach den Methoden der
vorher beschrieben Vererbung mit Template-Parametern wurde Colsamm um eine zentrale Domain -
Klasse implementiert, die mit den entsprechenden Template-Parametern von den Funktionseinheiten
(den Funktionsklassen) erbt. Benutzerdefinierte Finite Elemente werden dann von der Domain -Klasse
mit den gewunschten Parametern abgeleitet, erben also die Methoden der Basisklassen. Zur Verein-
fachung dieser Definitionen – es sollen nicht jedes mal alle Template-Parameter spezifiziert werden –
sind fur bestimmte Kategorien von Elementen Ableitungen der Klasse Domain eingefuhrt, die bereits
fur gewisse Kategorien einen Teil der Templates festsetzen (siehe Abschnitt B.2.7). Abbildung B.1
beschreibt den generellen Aufbau der Klassenhierarchie in Colsamm.
127
ANHANG B. COLSAMM – PROGRAMMSTRUKTUR
Abbildung B.1: Klassenhierarchie von Colsamm.
B.2.1 Domain
Wie aus der Abbildung B.1 deutlich wird, laufen die Funktionalitaten der funf Basisklassen in Domain
zusammen. Diese Klasse erbt die Methoden und nutzt die jeweilige template-abhangige Implemen-
tierung der Formeln bzw. der Speicherverwaltung der jeweiligen Basisklassen. Zur Steuerung dieser
Template-Vererbung besitzt Domain insgesamt zwolf Template-Parameter, die wie folgt angeordnet
sind.
Listing B.1: Liste der Template-Parameter der Klasse Domain .
template < uns igned i n t co rne r sOfE l ement ,
DIMENSION i n i t d im e n s i o n ,
c l a s s TRAFO,
d oma i n S e c i f i c a t i o n i n t e r i o r I b o u n d a r y ,
uns igned i n t numberOfBas i sFunct ions ,
uns igned i n t numberOfBas i sFunct ions2 ,
ElementType elType ,
I n t e g r a t i o nA c c u r a c y intAcc ,
typename ETYPE,
s t e n c i l T y p e STENCIL ,
typename eTYPE ,
Bas i sFunct i onType BFnType>
c l a s s Domain ;
Im Folgenden werden diese Template-Argumente genauer erlautert, insbesondere welchen Auswir-
kungen und Eigenschaften diese fur die Funktionsweise von Colsamm haben.
• unsigned int cornersOfElement: die Anzahl der Diskretisierungspunkte, die fur die jeweilige Transfor-
mation benotigt werden, um ein allgemeines Element zu definieren. Dies konnen im Falle von
isoparametrischen Elementen mehr als die Anzahl der Eckpunkte des Referenzelements sein. Da-
gegen sind auch weniger Diskretisierungspunkte moglich, etwa bei regularen Wurfeln, wo vier
anstatt acht Punkte genugen um die Transformationen zu bestimmen.
• DIMENSION init dimension: Enumerationstyp mit den Werten D1, D2, D3, beschreibt die Dimension
des Elements. Diese Dimension bezieht sich auf die allgemeinen Elemente des Problems, nicht
die des Referenzelements. Also besitzen die Oberflachendreiecke im dreidimensionalen auch Di-
mension drei (D3), das Referenzelement (Unit Triangle ) ist aber zweidimensional.
• class TRAFO: stellt einen allgemeinen Template-Typen dar, der einen FET-Ausdruck mit Wrapper
VertexExpr<.> benotigt. Diese Ausdrucke entstehen durch die Kombination von Punkten und raum-
lichen Variablen mit einer anschließenden Typkapselung mittels dem Makro Define Transformation,
siehe auch 11.4.1.
128
ANHANG B. COLSAMM – PROGRAMMSTRUKTUR
• domainSpecification interiorIboundary umfasst einen Enumerationstyp mit den Werten interior oder
boundary. Diese Werte dienen zur Unterscheidung von Elementen im Inneren bzw. am Rand, da
jeweils andere Integrationsformeln verwendet werden mussen, siehe Abschnitt 10.1.4. Im Falle von
inneren Elementen sind dies die Transformationsformel mit der Multiplikation der Determinanten
der Jacobi-Matrix. Bei Randelementen wird das Integral mittels Einsetzen der Parametrisierung
und Multiplikation mit der Lange der Parametrisierung berechnet.
• unsigned int numberOfBasisFunctions und unsigned int numberOfBasisFunctions2 bezeichnen die Anzah-
len der Basisfunktionen in den Funktionenmengen. Dabei muss die erste dieser beiden Mengen
(numberOfBasisFunctions) mindestens Große 1 besitzen. Dagegen kann die zweite Funktionenmenge
auch leer sein und keine Funktionen enthalten. Diese zweite Menge dient zur Definition gemisch-
ter Finiter Elemente (siehe auch Abschnitt 10.2.3). Intern wird aus diesen Werten bestimmt,
wieviele Mengen an Basisfunktionen verwendet werden.
• ElementType elType bezeichnet einen Enumerationstyp mit moglichen Werten Unit Cuboid, Unit Prism,
Unit Pyramid, Unit Tetrahedron, Unit Interval . Unit Triangle , Unit Quadrangle. Diese beziehen sich auf die
zugehorigen Geometrien der vordefinierten Referenzelemente in den verschiedenen Dimensio-
nen. Fur diese Elemente sind in Klassenspezialisierungen die entsprechenden Gaußpunkte und
-gewichte fur unterschiedliche Genauigkeiten enthalten.
• IntegrationAccuracy intAcc ist ein Enumerationstyp mit Gauss1, Gauss2, Gauss3. Diese Werte entspre-
chen den Integrationsgenauigkeiten auf dem jeweiligen Referenzelement. Dabei werden bei Gauss1
lineare Polynome, bei Gauss2 Polynome vom Grad 3 und bei Gauss3 Polynome vom Grad 5 exakt
integriert. Die Gaußquadraturen liefern des Weiteren gute Approximationen fur Integrale, deren
Integranden keine Polynome sind (siehe Abschnitt 10.1.5).
• typename ETYPE stellt den mathematischen Grunddatentyp der resultierenden Diskretisierungs-
sterne dar. Per Default ist dieses Template auf double gesetzt, kann aber jeden beliebigen Daten-
typ annehmen, fur den die Grundrechenarten, Wurzel (sqrt) und Betrag (fabs bzw. abs) definiert
sind. Diese konnen also auch komplexwertig sein.
• StencilType STENCIL beschreibt einen Enumerationstyp mit den Werten STLVector oder Array. Diese
Werte stehen fur die zugrunde liegende Datenstruktur der resultierenden Diskretisierungssterne,
die entweder aus STL Vektoren oder dynamischen Feldern aufgebaut sein konnen.
• typename eTYPE umfasst einen der fundamentalen Datentypen float oder double und stellt einen
internen Datentypen fur die Datenstrukturen der Gaußpunkte und Koordinaten der Elemente
dar. Dabei ist zu beachten, dass eType ein Datentyp sein muss, der in den Typ ETYPE umgewandelt
werden kann.
• BasisFunctionType BFnType beschreibt einen Enumerationstyp mit den beiden Werten onedimensional
bzw. vectorBasisFunctions . Diese dienen zur Unterscheidung zwischen skalarwertigen Basisfunktio-
nen und den vektorwertigen Kantenelementen, siehe Abschnitt 10.2.4. Denn in diesen Fallen sind
unterschiedliche Berechnungen fur die transformierten Integranden anzuwenden.
Nach dieser Beschreibung der Template-Parameter der Klasse Domain werden nun noch die wichtigen
Methoden zusammengestellt. Diese sind zum Teil in der Klasse selbst definiert, bzw. werden von ei-
ner der funf Funktionsklassen geerbt. Die in den folgenden Ausfuhrungen erwahnten Listen-, Vektor-,
Matrix- bzw. Tensor-Datenstrukturen beschreiben Container, deren Elemente sich uber den Indexope-
rator operator[ ] ansprechen lassen.
• Zur Definition neuer Elemente sind die Basisfunktionen auf dem Referenzelement einzufuhren.
Dazu dient die Funktion Set, die als Argument Ausdrucke vom Typ FunctionExpr erhalt, siehe dazu
129
ANHANG B. COLSAMM – PROGRAMMSTRUKTUR
Abschnitt 11.4.2. Dabei kann spezifiziert werden, in welche Menge die Funktionen eingetragen
werden, per Default in die erste Funktionenmenge. Zusatzlich sind fur Kantenelemente die Eck-
punkte der Kante anzugeben, auf welcher die Funktion einen Freiheitsgrad beschreibt (mittels
Index in der lokalen Nummerierung auf dem Referenzelement, siehe z.B. Listing 11.5).
template < c l a s s Func t i onExp r e s s i on >
i n l i n e vo id Set ( const Func t i onExp r e s s i on & a ) ;
template < c l a s s Func t i onExp r e s s i on >
i n l i n e vo id Set ( Ba s i s Func t i onSe t setNumber , const Func t i onExp r e s s i on & a ) ;
template < c l a s s Func t i onExp r e s s i on >
i n l i n e vo id Set ( const Func t i onExp r e s s i o n & a ,
uns igned i n t cnr 1 , uns igned i n t cn r 2 ) ;
• Die Koordinaten der Diskretisierungspunkte fur die Bestimmung der Transformation, werden
uber den Klammeroperator an das Finite Element ubergeben. Diese Koordinaten mussen sequen-
tiell als Liste in der Form x0,y0,z0,x1,y1,z1 ,... abgespeichert sein. Intern werden aus diesen Koor-
dinaten die Transformationsformel sowie deren Ableitungen berechnet. Falls fur das Element we-
niger als die mittels des entsprechenden Template-Parameters angegebenen Punkte fur die Trans-
formation benotigt werden, kann dies durch das zweite Argument eingeschrankt werden. Die Me-
thode gibt ein Domain -Objekt zuruck, welches zur Vereinfachung mit DOMAIN WITH TEMPLATES
bezeichnet ist.
template < c l a s s PointArrayTYPE >
i n l i n e DOMAIN WITH TEMPLATES&
ope ra to r ( ) ( const PointArrayTYPE& cc , const i n t numberPoints=corne r sOfE l ement ) ;
• Die numerische Integration der schwachen Formulierung auf einem Finiten Element wird durch die
integrate -Funktionen durchgefuhrt, wobei die ubergebenen Integranden vom Typ BasisFunctionExpr
sein mussen (siehe Abschnitt 11.3). Große und Dimension der Steifigkeitsmatrix berechnen sich
aus den Basisfunktions-Platzhaltern im Integranden.
Fur die Durchfuhrung der Integration (nach der vorgegebenen Genauigkeit) werden zwei Me-
thoden angeboten. Die erste Version hat den resultierenden Diskretisierungsstern als Ruckgabe-
wert, der jedoch aus Effizienzgrunden nicht kopiert, sondern by reference ubergeben wird. Fur
diesen Stern (STENCIL), der aus dem durch StencilType festgelegten Datentyp aufgebaut ist, re-
serviert Colsamm den notigen Speicher. Die zweite Variante speichert den Diskretisierungsstern
in einer benutzerdefinierten Tensor-Datenstruktur, die als Funktionsargument ubergeben wird
(StencilType& stencil ).
template < c l a s s I n t eg r and >
i n l i n e STENCIL& i n t e g r a t e ( const Bas i sFunc t i onExp r < I n t eg r and > & a ) ;
template < typename StenType , c l a s s I n t eg r and >
i n l i n e vo id i n t e g r a t e ( StenType& s t e n c i l , const Bas i sFunct i onExpr <I n teg rand >& a ) ;
• Neben der Integration kann auch eine Auswertung an vorgegebenen Punkten im Element imple-
mentiert werden. Dies wird beispielsweise bei der Berechnung von Dirac-Funktionen benotigt.
Die Große des Ergebnistensors wird dabei wiederum aus den Platzhaltern in der ubergebenen
Funktion bestimmt (Abschnitt 11.3).
template < c l a s s I n t eg r and >
i n l i n e STENCIL TYPE& va l u e ( const Bas i sFunct i onExpr <I n teg rand > & a ) ;
• Um diese Auswertung von Funktionen an beliebigen Stellen im Element zu ermoglichen, konnen
mittels Point die Koordinaten zusatzlicher Punkte an das Element ubergeben werden. Fur diese
130
ANHANG B. COLSAMM – PROGRAMMSTRUKTUR
werden dann intern alle notigen Berechnungen durchgefuhrt. Die Funktion Point Test testet nach
dem Setzen zusatzlich, ob er Punkt innerhalb des aktuellen Elements liegt.
template < c l a s s PointArrayTYPE >
vo id Po i n t ( const PointArrayTYPE & po i n t ) ;
template < c l a s s PointArrayTYPE >
boo l Po i n t Te s t ( const PointArrayTYPE & po i n t ) ;
• Zuletzt werden noch einige Methoden zusammengefasst, die Informationen uber die gesetzten
Template-Paramater liefern. Die folgenden Funktionen geben die Großen der entsprechenden
Basisfunktionenmengen zuruck.
uns igned i n t s i z e S e t 1 ( ) const ;
uns igned i n t s i z e S e t 2 ( ) const ;
Des Weiteren eine Methode zur Bestimmung der Anzahl der definierten Diskretisierungspunkte.
uns igned i n t getNumberCorners ( ) const ;
Schließlich eine Funktion, welche die Dimension des Elements zuruckgibt.
uns igned i n t d imens i on ( ) const ;
Nachfolgend werden die Eigenschaften der funf Funktionsklassen kurz zusammengefasst, ohne dabei
genauer auf deren Realisierung einzugehen.
B.2.2 Gaussian Points
Fur die Anwendung der Gauß’schen Quadraturformel werden je nach Integrationsgenauigkeit und Geo-
metrie des Referenzelements die entsprechenden Stutzstellen und Gewichte benotigt [SB02]. Da die ver-
schiedenen Integrationsstufen der einzelnen Elemente anhand der Template-Parameter unterscheidbar
sind, sind fur jede notige Kombination spezialisierte Versionen der Klasse Gaussian Points implemen-
tiert. Diese beinhalten dann bereits die entsprechenden Stutzstellen und Gewichte. Daruber hinaus ist
konnen diese bereits implementierten Klassen einfach durch zusatzliche Spezialisierungen auf weitere
Integrationsgenauigkeiten und -gebiete erweitert werden.
B.2.3 Basis Function
Fur die FE-Diskretisierungen sind Freiheitsgrade zu definieren. Die Lage dieser Freiheitsgrade kann
dabei durch die Formeln der Basisfunktionen auf dem Referenzelement festgelegt werden. Die Basis-
funktionen, die mit der Methode Set gesetzt werden, werden intern mittels abstrakter Klasse (Abschnitt
7.3) abgespeichert. Die Performance dieser Implementierung ist zwar nicht optimal, doch werden die
Basisfunktion lediglich einmalig bei der Initialisierung des Objektes an den Gaußpunkten ausgewertet,
so dass dies kaum ins Gewicht fallt. Die berechneten Werte der Basisfunktionen und deren Ableitungen
an den Gaußpunkten im Referenzelement werden intern abgespeichert. Da es sich hierbei nur um rela-
tiv geringe Datenmengen handelt, auf die schnell und oft zugegriffen werden muss, werden die Daten
in Feldern (auf dem Stack) angelegt.
B.2.4 Mapping
Die Template-Klasse Mapping dient zur Berechnung und Speicherung der Werte der Transformation,
deren Ableitungen sowie der Determinante bzw. Lange der Normalen an den Gaußpunkten. Je nach
Dimension des Finiten Elements sind fur die Durchfuhrung der Kettenregel und Bestimmung der
131
ANHANG B. COLSAMM – PROGRAMMSTRUKTUR
Inversen der Jacobi-Matrix unterschiedliche Formeln anzuwenden. Gleiches gilt auch fur die Vorbe-
rechnungen der Jacobi-Determinante bzw. der Lange des Normalenvektors, wobei die Unterscheidung
zwischen Berechnungen im Inneren oder am Rand. Da diese Berechnungen fur regulare Anwendungen
haufig durchgefuhrt werden mussen (einmal pro Element), wurde hierzu die schnelle Auswertung uber
FET gewahlt (Abschnitt 6.2).
Auch hier konnen durch Spezialisierungen weitere Abbildungsklassen definiert werden, beispielsweise
auch fur vierdimensionale Berechnungen.
B.2.5 CornerClasses
Die Klasse CornerClasses stellt den Speicher fur die Koordinaten der Finiten Elemente bereit. Dies
konnen, z.B. bei isoparametrischen Elementen, mehr sein als die Eckenzahl der zugrunde liegenden
Referenzgeometrie, da durch die entsprechende Transformation Zwischenpunkte auf die Kanten des
Referenzelements abgebildet werden.
B.2.6 StencilTypeInit
Je nach Problemstellung der PDG auf dem Finiten Element entstehen null- bis dreidimensionale lokale
Diskrestisierungssterne. Diese Dimension hangt dabei von den (Differential-)Operatoren der schwachen
Formulierung der PDG ab, und die Große der in der jeweiligen Dimension von der Anzahl der Basisfunk-
tionen, siehe auch Abschnitt 11.4.2. Daruber hinaus ist der implementierte Mechanismus auf weitere
Datentypen erweiterbar. Dazu sind lediglich eine weitere Spezialisierungen der StencilyTypeInit -Klasse
fur entsprechende Datentypen einzufuhren.
B.2.7 Simple Element, Mixed Element, Vectorial Element
Zur Definition von speziellen Finiten Elementen muss eine entsprechende Element-Klasse definiert
werden, welche
1. von der Klasse Domain mit entsprechenden Template-Parametern erbt, und
2. einen Default-Konstruktor definiert, welcher die Definitionen der Basisfunktionen auf dem Refe-
renzelement vornimmt.
Damit nicht fur jede Definition alle Parameter angegeben werden mussen, existieren sog. Zwischen-
elemente, welche bereits einen Teil der Template-Parameter setzen. Neu eingefuhrte Elemente konnen
dann wiederum von diesen Zwischenelementen erben, und erhalten so die volle Funktionalitat der
Klasse Domain . Falls dennoch Elemente definiert werden sollen, die mit den Zwischenelementen nicht
beschreibbar sind, so konnen weiterhin eigene Ableitungen von Domain eingefuhrt werden. Zusatz-
lich dienen wie vorher die Default-Template-Parameter dazu, dass nur die elementspezifischen Daten
gesetzt werden mussen, sofern die Default-Werte fur die Anwendung passen.
• Die Klasse Simple Element dient zur Definition von inneren Finiten Elementen mit nur einer Menge
von reellwertigen Ansatz- bzw. Testfunktionen. Entsprechend muss hierbei nur noch die Große
einer Funktionenmenge angegeben werden, die zweite wird intern auf null gesetzt.
template< i n t numberCorners , DIMENSION dim ,
c l a s s TRAFO, i n t numberOfFunctions ,
ElementType elType , I n t e g r a t i o nA c c u r a c y i n tAcc = Gauss2 ,
typename ETYPE = double , s t e n c i l T y p e STENCIL = STLVector ,
typename eTYPE = double >
c l a s s S imp l e E l ement ;
132
ANHANG B. COLSAMM – PROGRAMMSTRUKTUR
• Analog dazu beschreibt die Klasse Simple Boundary Element die Template-Parameter fur Randele-
mente mit nur eine Menge von Basisfunktionen. Auch hier wird die Große der zweiten Menge
intern auf null gesetzt.
template< i n t numberCorners , DIMENSION dim ,
c l a s s TRAFO, i n t numberOfFunct ions ,
ElementType elType , I n t e g r a t i o nA c c u r a c y i n tAcc = Gauss2 ,
typename ETYPE = double , s t e n c i l T y p e STENCIL = STLVector ,
typename eTYPE = double >
c l a s s S imp l e Bounda ry E l ement ;
• Fur gemischte Finite Elemente Ansatze dient die Klasse Mixed Element. Hierbei werden die Großen
von zwei Mengen von Basisfunktionen definiert. Dieses Zwischenelement ist noch nicht als inneres
oder Randelement festgelegt.
template< i n t numberCorners , DIMENSION dim ,
c l a s s TRAFO, i n t numberOfFunct ions ,
i n t numberOfFunctions2 , ElementType elType ,
I n t e g r a t i o nA c c u r a c y i n tAcc = Gauss2 ,
d oma i nSp e c i f i c a t i o n i n t e r i o r I b o u n d a r y ,
typename ETYPE = double , s t e n c i l T y p e STENCIL = STLVector ,
typename eTYPE = double >
c l a s s Mixed Element ;
• Als letztes Zwischenelement dient die Klasse Vectorial Element zur einfacheren Definition von vek-
torwertigen Finiten Elementen, siehe Abschnitt 10.2.4. Hierbei ist ebenfalls nur eine Menge von
Basisfunktionen einzufuhren.
template< i n t numberCorners , DIMENSION dim , c l a s s TRAFO,
i n t numberOfFunct ions , ElementType elType ,
I n t e g r a t i o nA c c u r a c y i n tAcc = Gauss2 ,
d oma i nSp e c i f i c a t i o n i n t e r i o r I b o u n d a r y ,
typename ETYPE = double , s t e n c i l T y p e STENCIL = STLVector ,
typename eTYPE = double >
c l a s s Ve c t o r i a l E l emen t ;
Alle in Colsamm vordefinierten Finiten Elemente wurden mit diesem Mechanismus als (indirekte)
Ableitung der Klasse Domain realisiert.
133
ANHANG B. COLSAMM – PROGRAMMSTRUKTUR
134
ANHANG C. PROGRAMMIERUNG VON TYPLISTEN
C Programmierung von Typlisten
Type lists (Typlisten) dienen zur Speicherung von Typen in Listen. Die nachfolgenden Implemen-
tierungen mit Template-Spezialisierungen und Traits basieren auf den Prinzipien der funktionalen
Programmierung [Ale01].
Eine Typliste (Typelist ) besteht aus dem Kopf und dem Rest der Listet. Der Kopf beschreibt dabei
das gespeicherte Element (einen Datentyp) und der Rest ist entweder wieder eine Typliste oder ein
NullType. Mit dem Makro TPYELIST wird eine Typliste aus einem Element generiert.
template <c l a s s H, c l a s s T>
s t r u c t T yp e l i s t
t ypede f H Head ;
t ypede f U Ta i l ;
;
c l a s s Nul lType ;
#d e f i n e TYPELIST (ENTRY) Type l i s t <ENTRY, Nul lType>
Zur Bestimmung der Lange einer Typliste wird die Liste rekursiv uber die Typen bis zum abschlie-
ßenden NullType durchlaufen. Fur jeden Eintrag der Typliste wird 1 addiert.
template <c l a s s TList>
s t r u c t Length ;
template <>
s t r u c t Length<Nul lType >
enum v a l u e = 0 ;
;
template <c l a s s H, c l a s s T>
s t r u c t Length<Type l i s t <H,T> >
enum v a l u e = 1 + Length<T> : : v a l u e ;
;
Um einen neuen Typ an eine bestehende Typliste anzuhangen, wird durch die Liste bis zum NullType
gelaufen und dann ein neuer Eintrag generiert. Ist der neue Eintrag selbst der NullType, so wird nichts
eingetragen.
template <c l a s s TList , c l a s s ENTRY>
s t r u c t Append ;
template <>
s t r u c t Append<Nul lType , Nul lType>
t ypede f Nul lType LIST ;
;
template <c l a s s ENTRY>
s t r u c t Append<Nul lType ,ENTRY>
t ypede f TYPELIST (ENTRY) LIST ;
;
template <c l a s s Head , c l a s s Ta i l >
s t r u c t Append<Nul lType , Type l i s t <Head , Ta i l> >
t ypede f Type l i s t <Head , Ta i l> LIST ;
;
template <c l a s s Head , c l a s s Ta i l , c l a s s ENTRY>
s t r u c t Append<Type l i s t <Head , Ta i l >,ENTRY>
t ypede f Type l i s t <Head , typename Append<Ta i l ,ENTRY> : : LIST> LIST ;
;
135
ANHANG C. PROGRAMMIERUNG VON TYPLISTEN
Beim Verschmelzen zweier Typlisten werden die Elemente der ersten Liste sukzessive in die zweite
Liste eingetragen.
template <c l a s s TList1 , c l a s s T l i s t 2 >
s t r u c t Jo in ;
template <>
s t r u c t Jo in<Nul lType , Nul lType >
t ypede f Nul lType LIST ;
;
template <c l a s s T>
s t r u c t Jo in<Nul lType ,T>
t ypede f T LIST ;
;
template <c l a s s Head , c l a s s Ta i l , c l a s s TList2>
s t r u c t Jo in<Type l i s t <Head , Ta i l >, TLi s t2 >
t ypede f typename Jo in<Ta i l ,
typename Append<TList2 , Head > : : LIST > : : LIST LIST ;
;
Soll eine Eintrag bei seinem ersten Auftreten in der Liste geloscht werden, so wird solange durch die
Liste gelaufen, bis der Kopf der aktuellen Teilliste mit dem zu loschenden Eintrag ubereinstimmt.
template <c l a s s TList , c l a s s ENTRY>
s t r u c t Era se ;
template <c l a s s ENTRY>
s t r u c t Erase<Nul lType ,ENTRY>
t ypede f Nul lType LIST ;
;
template <c l a s s ENTRY, c l a s s Ta i l>
s t r u c t Erase<Type l i s t <ENTRY, Ta i l >,ENTRY>
t ypede f Ta i l LIST ;
;
template <c l a s s Head , c l a s s Ta i l , c l a s s ENTRY>
s t r u c t Erase<Type l i s t <Head , Ta i l >,ENTRY>
t ypede f Type l i s t <Head ,
typename Erase<Ta i l ,ENTRY> : : LIST> LIST ;
;
Zum Erstellen einer Liste ohne Duplikate wird rekursiv eine Liste ohne Duplikate erzeugt, indem
der Kopf der aktuellen Teilliste aus dem Rest geloscht wird. Dieser ist selbst bereits eine Liste ohne
Duplikate, so dass ein Loschen beim ersten Auftreten genugt.
template <c l a s s TList>
s t r u c t NoDup l i ca te s ;
template <>
s t r u c t NoDupl i cates <Nul lType >
t ypede f Nul lType LIST ;
;
template <c l a s s Head , c l a s s Ta i l>
s t r u c t NoDupl i cates <Type l i s t <Head , Ta i l > >
p r i v a t e :
t ypede f typename NoDupl i cates <Ta i l > : : LIST L1 ;
t ypede f typename Erase<L1 , Head > : : LIST L2 ;
pub l i c :
t ypede f Type l i s t <Head , L2> LIST ;
;
Zum Abschluss wird noch die Klasse CountVariables definiert, die in Abschnitt 12.3 verwendet wird,
um die Anzahl an verschiedenen nummerierten Vektoren zu bestimmen. Da bei den Variablen, die
im Cache gehalten werden sollen auch der Ergebnisvektor berucksichtigt werden muss, wird dieser
zunachst der Typliste des Ausdrucks hinzugefugt. Aus der resultierenden Typliste werden anschließend
alle Duplikate entfernt. Der Enumerationswert value enthalt dann die Lange der so erstellten Typliste.
136
ANHANG C. PROGRAMMIERUNG VON TYPLISTEN
template <c l a s s A, c l a s s B>
s t r u c t Coun tVa r i a b l e s
t ypede f typename NoDupl i cates <
typename Jo in<typename A : : VARIABLE LIST ,
typename B : : VARIABLE LIST > : : LIST > : : LIST
RESULT ;
enum v a l u e = Length<RESULT> : : v a l u e ;
;
137
ANHANG C. PROGRAMMIERUNG VON TYPLISTEN
138
ANHANG D. VERWENDETE COMPUTER-PLATTFORMEN
D Verwendete Computer-Plattformen
D.1 Arbeitsplatzrechner
Auf den nachfolgend aufgefuhrten Worstations wurde mit unterschiedlichen Versionen des GNU-C++-
Compilers gerarbeitet (Versionen 3.3.2 bis 4.1.0).
D.1.1 Pentium 4
• CPU: 3.0 GHz, P4, Hyperthreading
• Peak-Performance: mittels SSE2 6 GFlops
• Cache: L2 512 kB
• RAM: 2 GByte
D.1.2 Dual-Core
• CPU: 2 x Intel(R) Core(TM)2 CPU 6600, 2.40GHz
• Peak-Performance: mind. 4.8 GFlops pro Kern, also insgesamt 9.6 GFlops
• Cache: L2 4 MB
• RAM: 4 GByte
D.2 Hochleistungsrechner
D.2.1 NEC SX-6/48M6
Die NEC SX-6/48M6 ist eine shared memory Vektorplattform. Die Berechnungen wurden mittels au-
tomatischer Vektorisierung auf einem Knoten durchgefuhrt [HLR05].
• Hochstleistungsrechenzentrum Stuttgart (HLRS)
• 6 Knoten mit je 8 CPUs, je 565 MHz
• Peak-Performance: 9 GFlops auf einem Knoten mit 8 CPUs (da Addition und Multipliktion in
einem Takt)
• RAM: 16 GB pro CPU
• Compiler: sc++ von NEC
139
ANHANG D. VERWENDETE COMPUTER-PLATTFORMEN
D.2.2 Hitachi SR8000-F1/168
Die Maschine bestand aus 168 Pseudo-Vektor-Knoten, die wiederum aus je acht effektiv nutzbaren
CPUs bestanden [Hit98]. Auf jedem Knoten konnten Gleitpunktoperationen auf den acht Prozessoren
parallel abgearbeitet werden (SIMD).
• LRZ Garching, 2006 stillgelegt
• 168 Knoten mit je 8 CPUs, je 375 Mhz
• Peak-Performance: 12 GFlops auf einem Knoten mit 8 CPUs
• RAM: 8 GB pro Knoten
• Compiler: sCC von Hitachi
D.2.3 Cluster
Der AMD-Opteron-Cluster am Lehrstuhl Informatik 10, Erlangen besteht aus 9 Dual- und 8 Quad-
Knoten [Dep05]. Alle Knoten sind mit einer GBit-Schnittstelle verbunden, die Vierfachknoten zusatz-
lich mit Infiniband. Dieses ermoglicht eine Ubertragungsbandbreite von 10 GBit/s.
Dual- bzw. Quad-Knoten:
• 2 bzw. 4 Rechenkerne
• CPU: 2.2 GHz AMD Opteron 248
• Peak-Performance: mittels 1 MultAdd/Takt = 2 Flops/Takt = 4.4 GFlops folgen insgesamt 17.6
GFlops
• Cache: L2 1 MB
• RAM: DDR 333, 4 GB fur Dual-Knoten, 16 GB fur Quad-Knoten yte
• Compiler: GNU-Compiler, Versionen 3.3.3, 4.0.1 bzw. 4.1.0, sowie Intel-C++-Compiler, Versionen
8.1 und 9.0
140
ANHANG E. CURRICULUM VITAE
E Curriculum Vitae
Jochen Hardtlein
03. August 2007
Allgemeine Informationen
Geburtstag: 22. Februar 1977
Geburtsort: Rothenburg ob der Tauber
Familienstand: ledig
Anschrift: Wildbadweg 1, Steinach / Ens
D–91605 Gallmersgarten
Email: [email protected]
WWW: www10.informatik.uni–erlangen.de/∼jochen
Ausbildung
Juli 2003–Marz 2007 Doktorand am Lehrstuhl Informatik 10,
Systemsimulation, Universitat Erlangen-Nurnberg
Nov. 2002–Juni 2003 Doktorand am Lehrstuhl fur Angewandte
Mathematik II, Universitat Wurzburg
Nov. 1997–Okt. 2002 Mathematik-Studium an der Universitat Wurzburg,
Nebenfach: Informatik, Abschluss: Diplom–Mathematiker
Aug. 1996–Okt. 1997 Ersatzdienst im Evang. Jugendheim in Rothenburg o.d.T
Sep. 1987–Juli 1996 Gymnasium Rothenburg ob der Tauber
Abschluss Allgemeine Hochschulreife (Abitur)
Anstellungsverhaltnisse
Nov. 2005– Jan 2006 Forschungsaufenthalt am Lawrence Livermore
National Laboratory, Kalifornien USA.
Juli 2003– Marz 2007 Wissenschaftl. Angestellter am Lehrstuhl Informatik 10,
Systemsimulation, Universitat Erlangen–Nurnberg
Nov. 2002–Juni 2003 Wissentschaftl. Angestellter am Institut fur
Angewandte Mathematik II, Universitat Wurzburg
141
ANHANG E. CURRICULUM VITAE
142
EIGENE VEROFFENTLICHUNGEN
Eigene Veroffentlichungen
[HLP05] J. Hardtlein, A. Linke und C. Pflaum. Fast Expression Templates. Computational Science
- ICCS 2005, Vol. 3515 LNCS, Seiten 1055 1063. Springer-Verlag, 2005.
[HLP06a] J. Hardtlein, A. Linke und C. Pflaum. Blocking Techniques with Fast Expression Tem-
plates . Technischer Bericht 06-0, Lehrstuhl fur Informatik 10, Systemsimulation, Uni-
versitat Erlangen-Nurnberg, 2006.
[HLP06b] J. Hardtlein, A. Linke und C. Pflaum. Fast Expression Templates - Enhancements for
High Performance C++ Libraries. Technischer Bericht 06-7, Lehrstuhl fur Informatik
10, Systemsimulation, Universitat Erlangen-Nurnberg, 2006.
[HLPW06] J. Hardtlein, A. Linke, C. Pflaum und C. H. Wolters. Advanced Expression Templates
Programming. Computing and Visualization in Science, Springer-Verlag, 2006. Einge-
reicht 2006, derzeit in Begutachtung.
[HP05] J. Hardtlein und C. Pflaum. Efficient and User-friendly Computation of Local Stiffness
Matrices. Frontiers in Simulation, Simulationstechnique, 18. Symposium in Erlangen,
2005.
[QVP+06] D. Quinlan, R. Vuduc, T. Panas, J. Hardtlein und A. Saebjoernsen. Support for Whole-
Program Analysis and the Verification of the One-Definition Rule in C++. NIST Special
Publication 500-262, Proceedings von Static Analysis Summit. Gaithersburg, MD, 2006.
[WKM+06a] C.H. Wolters, H. Kostler, C. Moller, J. Hardtlein und A. Anwander. Numerical Approa-
ches for Dipole Modeling in Finite Element Method based Source Analysis. Cheyne, D.,
Ross B., Stroink G., und Weinberg, H.(eds.), International Congress Series 1300, 2006.
in press.
[WKM+06b] C.H. Wolters, H. Kostler, C. Moller, J. Hardtlein, L. Grasedyck und W. Hackbusch.
Numerical mathematics of the subtraction method for the modeling of a current dipole in
EEG source reconstruction using finite element head models. SIAM Journal on Scientific
Computing, 2006. Eingereicht 2006, derzeit in Begutachtung.
143
EIGENE VEROFFENTLICHUNGEN
144
LITERATURVERZEICHNIS
Literaturverzeichnis
[AELS99] L. Alvarez, J. Escların, M. Lefebure und J. Sanchez. A PDE model for computing the optical
flow. Proc. XVI Congreso de Ecuaciones Diferenciales y Aplicaciones, pages 1349–1356, Las
Palmas de Gran Canaria, Spain, 1999.
[AG05] A. Abrahams und A. Gurtovoy. C++ Template Metaprogramming, Concepts, Tools, and
Techniques from Boost and Beyond. Addison-Wesley, 2005.
[Ale01] A. Alexandrescu. Modern C++ Design, Generic Programming and Design Patterns Applied.
Addison-Wesley, 2001.
[AS01] D. Abrahams und J. G. Siek. Policy Adaptors and the Boost Iterator Adaptor Library.
Second Workshop on C++ Template Programming, October 2001.
[ASU86] A.V. Aho, R. Sethi und J. D. Ullman. Compilers: Princiles, Techniques, and Tools. Addison-
Wesley, 1986.
[BCHQ97] D. Brown, G. Chesshire, W. Henshaw und D. Quinlan. OVERTURE: An Object-Oriented
Software System for Solving Partial Differential Equations in Serial and Parallel Environ-
ments, 1997. Eight SIAM Conference on Parallel Processing for Scientific Computing, Min-
neapolis, Minnesota.
[BDQ97] F. Basetti, K. Davis und D. Quinlan. C++ Expression Templates Performance Issues in
Scientific Computing, Oct 1997. CRPC-TR97705-S.
[BF01] R.L. Burden und J.D. Faires. Numerical Analysis. Brooks/Cole, seventh edition, 2001. 2
copies.
[Bra97] D. Braess. Finite Elements: Theory, Fast Solvers, and Applications in Solid Mechanics.
Cambridge University Press, second edition, 1997.
[Bru06] A. Bruhn. Variational Optic Flow Computation: Accurate Modeling and Efficient Nume-
rics. PhD thesis, Department of Mathematics and Computer Science, Saarland University,
Saarbrucken, 2006.
[BS94] S.C. Brenner und L. R. Scott. The Mathematical Theory of Finite Element Methods. Num-
ber 15 in Texts in Applied Mathematics. Springer, 1994.
[CBHR] C.Freundl, B. Bergen, F. Hulsemann und U. Rude. Parexpde: Expression templates and
advanced pde software design on the hitachi sr8000. In A. Bode und F. Durst, editors,
High Performance COmputing in Science and Engineering, Garching 2004. Springer. ISBN
3-540-26145-1.
[CDK+01] R. Chandra, L. Dagum, D. Kohr, D. Maydan, J. McDonald und R. Menon. Parallel Pro-
gramming in OpenMP. Academic Press, 2001.
[Cia02] P.G. Ciarlet. The Finite Element Method for Elliptic Problems. SIAM, 2002.
145
LITERATURVERZEICHNIS
[CL93] B. Cabral und L.C. Leedom. Imaging vector fields using line integral convolution. SIGGRAPH
’93: Proceedings of the 20th annual conference on Computer graphics and interactive techni-
ques, pages 263–270, New York, NY, USA, 1993. ACM Press.
[CSvS86] C. Cuvelier, A. Segal und A.A. van Steenhoven. Finite Element Methods and Navier-Stokes
Equations. D. Reidel Publishing Company, 1986.
[DA03] B. Dawes und D. Abrahams. BOOST , 2003. http://www.boost.org.
[Dep05] Department of Computer Science 10, System Simulation, Erlangen. HPC Cluster, 2005.
http://www10.informatik.uni-erlangen.de/Cluster/hpc.shtml.
[DJ03] Vandevoorde D und N.M. Josuttis. C++ Templates - The Complete Guide. Addison-Wesley,
2003. ISBN 0-201-73484-2.
[EV01] R. Eigenmann und M.J. Voss. OpenMP Shared Memory Parallel Programming, 2001. Lecture
Notes in Computer Science 2104 (Heidelberg: Springer-Verlag).
[Fra05] Fraunhofer IIS-B Erlangen. ORCAN - Open Reflective Component Architecture, 2005.
http://sourceforge.net/projects/orcan.
[Fur97] G. Furnish. Disambiguated Glommable Expression Templates. Computers in Physics, volume
11, No. 3, pages 263–269, May/June 1997.
[GG] J. Gil und Z. Gutterman. Compile Time Symbolic Derivation with C++ Templates. pages
249–262.
[GR05] C. Großmann und H. Roos. Numerische Behandlung partieller Differentialgleichungen. Teub-
ner, 2005.
[Han96] S. Haney. Beating the Abstraction Penalty in C++ Using Expression Templates. In P. Du-
bois, editor, Computers in Physics, volume 10, No. 6, pages 552–557. American Institute Of
Physics, Nov/Dec 1996.
[HCKS98] S. Haney, J. Crotinger, S. Karmesin und S. Smith. PETE: The Portable Expression Tem-
plates Engine, 1998. Dr. Dobb’s Journal of Software Tools, 24(10):88, pages 90–92, 94–95.
[Hen02] W. Henshaw. OVERTURE: An Object-Oriented Framework for Overlapping Grid Applicati-
ons, 2002. UCRL-JC-147889, Paper for the 2002 AIAA conference on Applied Aerodynamics,
St Louis, MO.
[Heu98] H. Heuser. Lehrbuch der Analysis - Teil 2 . Teubner, 1998.
[Hit98] Hitachi Ltd. The Hitachi SR8000 Series Super Technical Server, Homepage, 1998.
http://www.hitachi.co.jp/Prod/comp/hpc/eng/sr81e.html.
[HLR05] HLRS – High Performance Center Stuttgart. The NEC SX-6 Cluster Documentation, 2005.
http://www.hlrs.de/hw-access/platforms/sx6/user doc.
[HS81] B.K.P Horn und B.G. Schunck. Determining optical flow. Artificial Intelligence, (17):185–203,
1981.
[Jin02] Jianmingg Jin. The Finite Element Method in Electromagnetics – Second Edition. IEEE-
Press, Wiley-Interscience Publication, 2002.
[JL01] M. Jung und U. Langer. Methode der finiten Elemente fur Ingenieure. B.G. Teubner, 2001.
146
LITERATURVERZEICHNIS
[KA00] P. Knabner und L. Angermann. Numerik partieller Differentialgleichungen. Springer-Verlag,
2000.
[Koo03] Kooperation der Max-Plank Institute Leipzig/Munchen, Institut fur Biomagnetismus und
Biosignalanalyse Munster. NeuroFEM - A finite element software fast computation of the
forward solution in EEG/MEG source localisation, 2003. http://www.neurofem.com/.
[Kow04] M. Kowarschik. Data Locality Optimizations for Iterative Numerical Algorithms and Cellular
Automata on Hierarchical Memory Architectures. PhD thesis, Lehrstuhl fur Informatik 10
(Systemsimulation), Institut fur Informatik, Universitat Erlangen-Nurnberg, July 2004. SCS
Publishing House, ISBN 3-936150-39-7.
[Lan02] H. P. Langtangen. Computational Partial Differential Equations – Numerical Methods and
Diffpack Programming. Springer, 2002.
[LHKK79] C. L. Lawson, R. J. Hanson, D. R. Kincaid und F. T. Krogh. Algorithm 539: Basic Li-
near Algebra Subprograms for Fortran Usage [F1]. 5(3):324–325, September 1979. ACM
Transactions on Mathematical Software.
[LP03] A. Linke und C. Pflaum. Fast Expression Templates for the SR 8000 Supercomputer. 2003.
Proceedings of the Workshop on Parallel/High-Performance Object-Oriented Scientific Com-
puting (POOSC).
[LvG99] M. Lerch und J. Wolff von Gudenberg. Expression Templates for Dot Product Expressions.
5:69–80, 1999. Reliable Computing.
[Mas01] I. Masakatsu. MET – Matrix Expression Templates Homepage, 2001.
http://met.sourceforge.net.
[McP04] S. McPeak. Elsa: An Elkhound-based C++ Parser, 2004.
http://www.cs.berkeley.edu/ smcpeak/elkhound/sources/elsa/index.html.
[MN04] S. McPeak und G. Necula. Elkhound: A Fast, Practical GLR Parser Generator, 2004. Pro-
ceedings of Conference on Compiler Constructor (CC04).
[Mon03] P. Monk. Finite Element Methods for Maxwell’s Equations. Nmerical Mathematics and
Scientific Computation. Oxford Science Publications, 2003.
[MS99] M.Gnewuch und S.A.Sauter. Boundary integral equations for second orer elliptic boundary
value problems, 1999. Preprint No. 55, Max-Planck-Institut fur Mathematik in den Natur-
wissenschaften Leipzig.
[MS00] M. Muller und S. Schwarzer. Einfuhrung in C++, 2000. http://www.ica1.uni-
stuttgart.de/Courses and Lectures/C++/script/node24.html.
[Nag83] H.-H. Nagel. Constraints for the estimation of displacement vector fields from image se-
quences. Proc. Eighth International Joint Conference on Artificial Intelligence, volume 2,
pages 945–951, Karlsruhe, West Germany, 1983.
[Pet03] O. Petzold. Tiny Vector Matrix library using Expression Templates (TVMET), 2003.
http://tvmet.sourceforge.net.
[Pfl01] C. Pflaum. Expression Templates for Partial Differential Equations, 2001. Comput Visual
Sci 4, 1–8.
[PH67] R. Plonsey und D. Heppner. Considerations on Quasi-Stationarity in Electro-physiological
Systems, 1967. Bull.math.Biophys., 29, pp. 657–664.
147
LITERATURVERZEICHNIS
[Qui02] D. Quinlan. ROSE Project Semantic-Based Optimization. 2002. Math Inform. & Computer
Sci. Principle Investigator’s Mtg., Argonne National Labs, June 26.
[Rum96] B. Rumpe. A Formal Methodolgy for the Design of Distributed Object-Oriented Systems.
POOMA’96: Parallel Object-Oriented Methods and Applications, Santa Fe, 1996.
[SB02] J. Stoer und R. Bulirsch. Introduction to Numerical Analysis. Springer, third edition, 2002.
[SK97] H. Shen und D.L. Kao. UFLIC: a line integral convolution algorithm for visualizing unsteady
flows. IEEE Visualization, pages 317–322, 1997.
[SK98] Han-Wei Shen und David L. Kao. A new line integral convolution algorithm for visuali-
zing time-varying flow fields. IEEE Transactions on Visualization and Computer Graphics,
4(2):98–108, 1998.
[SL01] J. Siek und A. Lumsdaine. The Matrix Template Library, 2001. Homepage:
http://www.osl.iu.edu/research/mtl.
[SS00] J. Striegnitz und S. Smith. An Expression Template aware Lambda Function. First Workshop
on C++ Template Programming, Erfurt, Germany, 10 2000.
[SS04] A. Schmidt und K. Siebert. Design of Adaptive Finite Element Software – Thte Finite
Element Toolbox ALBERTA. Springer, 2004.
[Str00] B. Stroustrup. The C++ Programming Language. AT&T, 2000.
[THR05] J. Treibig, S. Hausmann und U. Rude. Performance Analysis of the Lattice Boltzmann
Method on x-86-64 Architectures. In F. Hulsemann, M. Kowarschik und U. Rude, editors,
18th Symposium Simulationstechnique ASIM 2005 Proceedings, volume 15 of Frontiers in
Simulation, pages 736–741. ASIM, SCS Publishing House, Sep 2005.
[TOS01] U. Trottenberg, C. Oosterlee und A. Schuller. Multigrid. Academic Press, 2001.
[Vel95] T. Veldhuizen. Expression Templates, 1995. C++ Report 7 (5), 26–31.
[Vel97] T. Veldhuizen. Will C++ be faster than Fortran?, 1997. Proceedings of the 1st International
Scientific Computing in Object-Oriented Parallel Environments (ISCOPE’97).
[Vel98] T. Veldhuizen. Expression Templates, 1998. Codebeispiele zu Expression Templates, Webpage
http://osl.iu.edu/∼tveldhui/papers/Expression-Templates/exprtmpl.html.
[Vel01] T. Veldhuizen. Blitz++ - Object Oriented Scientific Computing, Homepage, 2001.
http://oonumerics.org/blitz.
[VJ03] D. Vandevoorde und N. Josuttis. C++ Templates - The Complete Guide. Addison-Wesley,
2003.
[VS06] M. Volter und T. Stahl. Model-Driven Software Development : Technology, Engineering,
Management. John Wiley & Sons, June 2006.
[WK02] J. Walter und M. Koch. uBLAS, Boost C++ Libraries - Basic Linear Algebra, 2002.
http://www.boost.org/libs/numeric/ublas/doc/index.htm.
[ZT00a] O.C. Zienkiewicz und R.L. Taylor. The Finite Element Method, Vol. 1: The Basis. Butter-
worth Heinemann, 2000.
[ZT00b] O.C. Zienkiewicz und R.L. Taylor. The Finite Element Method, Vol. 2: Solid Mechanics.
Butterworth Heinemann, 2000.
148