[ < ] [ globale Übersicht ] [ Kapitelübersicht ] [ Stichwortsuche ] [ > ]

5.5 Beispiele zur Programmoptimierung

Das folgende Kapitel ist unmittelbar mit 5.4 Schleifentransformation verbunden. Es soll hier der Inhalt hier an konkreten Beispielen anschaulich gemacht werden.

5.5.1 Übersicht

Erinnerung
Basis
  Rechnerarchitektur
  Programmiersprache
  Compileroptimierungen
Transformationen
  Schleifenvertauschung
  Schleifen aufrollen
  Blockung
  Blockung mit Kopieren
  Blockung mit Kopieren und Schleifenaufrollen
Präcompiler und Optimierer
Anwendungsbeispiele von Bibliotheken
Zukünftige Entwicklung
Zugehörige Seiten und interessante Links
Macht's gut, und danke für den Fisch

5.5.2 Erinnerung

Für die gängisten Programmiersprachen sind die hier gezeigten Beispiele bereits So existiert als Example in FORTRAN 90 die vordefinierte Funktion MATMUL, für Matrizenmultiplikation. Derzeit gibt es sehr viele und umfangreiche Libraries, die für verschiedene Rechner und Sprachen portiert wurden. So das auch Ihr Anwendungsfall wahrscheinlich davon abgedeckt wird.
Die hier gezeigten Programmfragmente sind nur dazu gedacht das Prinzip näher zu bringen, und sind nicht zu direkt Einsatz gedacht.

5.5.3 Basis

In diesem Kapitel geht es ausschließlich um Optimierungen der Laufzeit. Jene von Compilezeiten und Programmgröße bleibt hier außen vor. Zunächst befassen wir uns mit Dingen die für das Verständnis nötig sind.

5.5.3a Rechnerachitektur

"Cache as Cash can."

Zu diesem Punkt, siehe auch ein bißchen 5.2 Ursachen geringer Effizienz.
Die Prozessoren sind um vieles schneller als Speicher. Um dieses Manko zu mildern, versucht man jene Informationen die häufiger benötigt werden, in einem schnelleren und teueren Zwischenspeicher (= Cache) zu halten. Greift der Prozessor nun auf Daten zu, die im Cache liegen (= Cache Hit), dann werden diese in sehr kurzer Zeit geliefert; nur wenn die die angeforderten Daten nicht im Cache sind (= Cache Miss), muß auf den langsameren Speicher zugegriffen werden.

Das Problem besteht darin eine Strategie zu finden, damit jene Speichereinheiten, die als Nächstes gebraucht werden, sich im Cache befinden.

Hierbei kommt einem die Tatsache das sich Speicherzugriffe innerhalb eines gewissen Bereiches wiederholen, zu Gute. D.h. ein Programm greift nicht mit gleicher Wahrscheinlichkeit auf all seinen Code zu (Prinzip der Lokalität). Da sind zwei verschiedene Arten von Lokalität:

Lokalität in Programmen treten aufgrund von einfachen und natürlichen Programmstrukturen auf.
Beispiel Lokalität
  • Befehle innerhalb von Schleifen und Rekursionen
Temporal
  • meist sequentielle Ausführung von Befehlen
Spatial
  • Daten von Arrays und Strukturen
Spatial
Einen Vergleich mit einer Bücherei und tiefergehende Informationen zum Thema Speicherhierachie finden Sie in [Paterson, 1994] .

Die Idee der Programmoptimierung: Jene Operationen bei denen dieses Schema der Lokalitat nicht gilt, so zu verändern daß auch diese vom Zwischenspeicher profitieren.

Diese Speicherhierachie ist die Grundlage aller hier vorgestellten Geschwindigkeitsverbesserungen. Somit wäre bei Einführung einer neuen Technologie, die zuerst im Speicherbereich umgesetzt werden würde, alles hier gesagte über Jahrzehnte hinweg belanglos.

5.5.3b Programmiersprache

Programmiersprachen können sich in vieler Hinsicht unterscheiden, und tun dies auch zumeist:

Zweidimensionales Array: So existieren bei der Angabe der Indizes beide Konventionen, sowohl in der Reihenfolge wie in der Mathematik üblich, als auch andersrum. Ebenso verschieden ist die Abbildung auf die Speicheraddressen.

Darstellung einer Matrix mit 2 Zeilen und 5 Spalten:

Speicherbelegung:

     (1)(2)(3)(4)(5)   (1)(2)(3)(4)(5)
    [(1)(2)(3)(4)(5)] [(1)(2)(3)(4)(5)]
Zugehöhrige Definitionen (h3):
Sprache Variablendefinition Verwendug
FORTRAN (p1) REAL, DIMENSION (2,5) :: mat (a1) mat(1,4)
C & C++ (p2) double mat[5][2]; mat[3][0]
Pascal VAR mat: ARRAY[1..2, 1..5] OF REAL; mat[1,4]
PL/I DCL mat(2, 5) BINARY FLOAT; mat(1,4)

5.5.3c Compileroptimierungen

Common Subexpression Elimination: Das "common subexpression elimination"-Modul erkennt Ausdrücke die mehr als einmal auftreten und das selbe Ergebnis bringen; berechnet das Ergebnis, und ersetzt jedes Vorkommen des Ausdrucks damit. Die Möglichkeiten des Teilausdrucks umspannen Befehle die Werte aus dem Speicher laden, ebenso gut wie arithmetische Berechnungen. Zum Beispiel, der Quelltext

     A = X + Y + Z
     B = X + Y + W
wird zu
     T1 = X + Y
     A = T1 + Z
     B = T1 + W

Loop Invariant Code Motion: Das "loop invariant code motion"-Modul erkennt Befehle innerhalb einer Schleife dessen Werte sich nicht ändern, und plaziert diese außerhalb der Schleife. Zum Beispiel, der Quelltext

     X = Z
     DO I = 1,10
	A(I) = 4 * X + I
     END DO
wird zu
     X = Z
     T1 = 4 * X
     DO I = 1,10
	A(I) = T1 + I
     END DO

Diese hier vorgestellten Optimierungen werden im folgenden als vorhanden angenommen. Eine vertiefende Darstellung dieser, wird anhand von später verwendeten Quelltexten veranschaulicht. Darüber hinaus, gibt es sehr viele weitere Möglichkeiten die sich zur Optimierung eignen.

Verschiedene weitere Quelltexte zur Veranschaulichung

In vielen Fällen müssen Sie aber den Compiler unterstützen, z.B. um klarzustellen, ob die Laufzeit oder der Speicherverbrauch wichtiger sind. Lesen Sie die allgemeinen, nicht speziell numerischen Hinweise zur Optimierung von FORTRAN-Quelltext, wenn Sie Ihrem FORTRAN-Compiler etwas entgegenkommen wollen.

Auf weitergehende automatische Optimierungen speziell für die numerische Mathematik wird später unter Präcompiler und Optimierer noch eingegangen.

5.5.4 Transformationen

Die vorgenommenen Leistungsmessungen erfolgten auf einer HP-Workstation. Reproduzierbar sind die Resultate nur auf völlig gleichartigen Systemen. Man kann sie jedoch qualitativ auf andere Systeme übertragen.

5.5.4a Schleifenvertauschung

Man vertausche innere und äußere Schleifen(-indizes) so, das weiterhin das selbe (etwas schneller) berechnet wird.

Beispiel: Multiplikation (h1) von 2 quadratischen (h2) Matrizen
Bei vielen Matrizenoperationen ist das errechnete Ergebnis nicht, von der Reihenfolge in welcher die Matrixelemente bearbeitet werden, abhängig.

    REAL, DIMENSION (qmax, qmax) :: a, b, c

    c = 0
    DO i = 1, qmax, 1
      DO j = 1, qmax, 1
        DO k = 1, qmax, 1
          c(i,j) = c(i,j) + a(i,k)*b(k,j)
        END DO
      END DO
    END DO

Dieser Algorithmus erfordert qmax3 Gleitpunktmultiplikationen. Weiters werden ebensoviele Additionen benötigt. Verfahren mit niedrigerer Komplexität finden Sie unter 4.5 Komplexität von Algorithmen und im Buch [Überhuber, 1995] Abschnitt 5.5.5.

Schleifen- Funktion innere Schrittweite
anordnung innerste mittlere äußere a b c
ijk SDOT SCPY - qmax 1 -
jik SDOT SCPY - qmax 1 -
kij SAXPY - - - qmax qmax
ikj SAXPY - - - qmax qmax
jki SAXPY - - 1 - 1
kji SAXPY - - 1 - 1
a,b,c ... Matrixvariablen im Programmcode.
SDOT ... Skalarprodukt von 2 Vektoren
SAXPY ... Vektor*Skalar+Vektor
innere Schrittweite ... Abstand der Matrixelemente im Speicher von einem Zugriff der innersten Schleife bis zum nächsten.

Da die Charakteristika im wesentlichen durch die innerste Schleife bestimmt werden, unterscheiden sich die Algorithmusvarianten mit demselben innersten Laufvariablen in ihrem Leistungsverhalten nur minimal. Die nachfolgende Abbildung enthält daher nur drei Varianten.

Bild

Bei der ijk-Variante ist die Anzahl der Cache-Schreibzugriffe im Verhältnis zu den Gleitpunktoperationen am geringsten, während dieses Verhältnis bei den anderen Beiden deutlich ungünstiger ist.
ijk ... 1 Schreibzugriff bei 2*qmax flop
jki ... qmax Schreibzugriffe bei 2*qmax flop
kij ... detto
Dies ist insoferne wichtig, da Schreibzugriffe meist eine längere Ausführungszeit benötigen.
Bei dem getesteten Rechner: 1,5 Zyklen für Schreiben, gegenüber
1 Zyklus beim Lesen und Gleitpunktrechnen

Auf Grund ihrer hohen Referenzlokalität ist aber die jki-Variante den beiden anderen eindeutig überlegen.

Für Matrixgrößen über 300 ist ein deutlicher Leistungseinbruch bei allen Varianten feststellbar (mehr Cache-Fehlzugriffe). Hierfür der Grund ist, daß die laufend benötigten Teile der drei Matrizen nicht mehr vollständig im Cache gehalten werden können.

5.5.4b Aufrollen von Schleifen

Man dupliziere den zu wiederholenden Teil und passt die Schleife entsprechend an. Dadurch kann man sich einerseits Rechenschritte sparen, z.B. die Prüfung der Schleifenbedingung erfolgt nur jedes n-te Mal. Weiters ist eine Parallelisierung dadurch vielfach möglich. Die Pipeline des Prozessors kommt dadurch auch stärker zum tragen.

i4k4j1-Variante

    REAL, DIMENSION (qmax, qmax) :: a, b, c

    c = 0
    DO i = 1, qmax, 4             ! Aufrolltiefe: 4 (= dreifach aufgerollt)
      DO k = 1, qmax, 4           ! Aufrolltiefe: 4
        DO j = 1, qmax, 1
          c(i  ,j) = c(i  ,j) + a(i  ,k  )*b(k  ,j)
          c(i  ,j) = c(i  ,j) + a(i  ,k+1)*b(k+1,j)
          c(i  ,j) = c(i  ,j) + a(i  ,k+2)*b(k+2,j)
          c(i  ,j) = c(i  ,j) + a(i  ,k+3)*b(k+3,j)

          c(i+1,j) = c(i+1,j) + a(i+1,k  )*b(k  ,j)
          c(i+1,j) = c(i+1,j) + a(i+1,k+1)*b(k+1,j)
          c(i+1,j) = c(i+1,j) + a(i+1,k+2)*b(k+2,j)
          c(i+1,j) = c(i+1,j) + a(i+1,k+3)*b(k+3,j)

          c(i+2,j) = c(i+2,j) + a(i+2,k  )*b(k  ,j)
          c(i+2,j) = c(i+2,j) + a(i+2,k+1)*b(k+1,j)
          c(i+2,j) = c(i+2,j) + a(i+2,k+2)*b(k+2,j)
          c(i+2,j) = c(i+2,j) + a(i+2,k+3)*b(k+3,j)

          c(i+3,j) = c(i+3,j) + a(i+3,k  )*b(k  ,j)
          c(i+3,j) = c(i+3,j) + a(i+3,k+1)*b(k+1,j)
          c(i+3,j) = c(i+3,j) + a(i+3,k+2)*b(k+2,j)
          c(i+3,j) = c(i+3,j) + a(i+3,k+3)*b(k+3,j)
        END DO
      END DO
    END DO
Die äußeren beiden Schleifen wurden je 3-fach aufgerollt, deswegen stehen 16 Anweisungen im Schleifenrumpf statt einer.

Durch das Aufrollen der Schleifen wird:

Leistungsvergleich zwischen:

Bild
Wie man sieht, ist i1k1j1 die davon ungünstigste, während die i8k8j1 gleichmäßig gute Leistungswerte liefert. Finden jedoch alle laufend benötigten Daten (also in unseren Beispiel sind das alle Elemente der Matrix B, vier Zeilen von C und 16 Elemente der Matrix A) im Cache Platz, erzielt man mit dem nur 3-fachen Aufrollen der i4k4j1 die besten Leistungsdaten.

Dieses bessere Leistungsverhalten der weniger stark aufgerollten Variante bei kleinen Matrizen, kommt durch die bessere Nutzung der Register zustande. Es werden ca. 30 Gleitpunktregister für Matrixelemente gegenüber von ca. 100 bei 7-fachen Schleifenaufrollen.

Da auf der verwendeten HP-Workstation nur 64 Gleitpunkt-Register zur Verfügung stehen, kann sich die Leistungsminderung durch Register-Umbelegungen bei der 7-fach-Aufroll-Version auswirken.

Folgerungen:



Matrizen kleine große
im Cache können im Cache gespeichert werden passen nicht mehr in den Cache
günstiger 3-faches Aufrollen 7-faches Aufrollen
Ursache bessere Registerausnutzung höhere Referenzlokalität

Bei Matrizen die nicht völlig in den Cache (n>200) bzw. in den Hauptspeicher passen (n>1600), brechen alle ikj-Varianten sehr stark ein. Hier zeigen die nun folgend gezeigten jki-Varianten weit besseres Verhalten.

j4k4i1-Variante

    REAL, DIMENSION (qmax, qmax) :: a, b, c

    c = 0
    DO j = 1, qmax, 4             ! Aufrolltiefe: 4 (= dreifach aufgerollt)
      DO k = 1, qmax, 4           ! Aufrolltiefe: 4
        DO i = 1, qmax, 1
          c(i,j  ) = c(i,j  ) + a(i,k  )*b(k  ,j  )
          c(i,j  ) = c(i,j  ) + a(i,k+1)*b(k+1,j  )
          c(i,j  ) = c(i,j  ) + a(i,k+2)*b(k+2,j  )
          c(i,j  ) = c(i,j  ) + a(i,k+3)*b(k+3,j  )

          c(i,j+1) = c(i,j+1) + a(i,k  )*b(k  ,j+1)
          c(i,j+1) = c(i,j+1) + a(i,k+1)*b(k+1,j+1)
          c(i,j+1) = c(i,j+1) + a(i,k+2)*b(k+2,j+1)
          c(i,j+1) = c(i,j+1) + a(i,k+3)*b(k+3,j+1)

          c(i,j+2) = c(i,j+2) + a(i,k  )*b(k  ,j+2)
          c(i,j+2) = c(i,j+2) + a(i,k+1)*b(k+1,j+2)
          c(i,j+2) = c(i,j+2) + a(i,k+2)*b(k+2,j+2)
          c(i,j+2) = c(i,j+2) + a(i,k+3)*b(k+3,j+2)

          c(i,j+3) = c(i,j+3) + a(i,k  )*b(k  ,j+3)
          c(i,j+3) = c(i,j+3) + a(i,k+1)*b(k+1,j+3)
          c(i,j+3) = c(i,j+3) + a(i,k+2)*b(k+2,j+3)
          c(i,j+3) = c(i,j+3) + a(i,k+3)*b(k+3,j+3)
        END DO
      END DO
    END DO
Bild

Hier sind kaum Leistungseinbußen bei sehr großen Matrizen festzustellen.

Schleifenaufrollen muß nicht in jeden Fall eine Verbesserung bringen. Die durch 3-faches Aufrollen der innersten Schleife entstehende j1k1i4-Variante zeigt ein noch schlechtere Leistung als die überhaupt nicht aufgerollte j1k1i1-Variante.

5.5.4c Blockung von Feldzugriffen

Jedes Element der ursprünglichen Matrix fällt genau in eine Untermatrix (auch Block). Der allgemeine Fall wird in [Überhuber, 1995a] Abschnitt 13.6.3 und ??? behandelt.
Zur einfacheren und lesbareren Darstellung wird im folgenden angenommen, daß die Matrizen in quadratische Untermatrizen zerlegt werden. Mit nb=n/b und mb=m/b; Die Zerlegung von A hat dann die Gestalt:

A=\left(\begin{array}{cccc}A_{11} & A_{12} & \ldots & A_{1b} \\ A_{21} & A_{22} & \ldots & A_{2b} \\ \vdots & \vdots & \vdots & \ddots & \vdots \\ A_{b1} & A_{b2} & \ldots & A_{bb} \end{array}\right)

wobei davon A11 folgendermaßen aussieht:

A_{11}=\left(\begin{array}{cccc}a_{11} & a_{12} & \ldots & A_{1m_{b}} \\ A_{21} & A_{22} & \ldots & A_{2m_{b}} \\ \vdots & \vdots & \vdots & \ddots & \vdots \\ A_{n_{b}1} & A_{n_{b}2} & \ldots & A_{n_{b}m_{b}} \end{array}\right)

Die restlichen Blöcke sehen analog dazu aus.

Will man einen Matrix-Algorithmus so umformen, daß eine bestimmte Ebene der Speicherhierachie gut ausgenutzt wird, so formuliert man diesen als Blockalgorithmus. Die Größe der Teilmatrizen ist dann so zu wählen, daß einerseits sämtliche beteiligeten Operandenblöcke in der betreffenden Speicherebene Platz finden, und andererseits die Anzahl der mit den Elementen dieser Blöcke ausgeführten Operationen maximal wird.

Hierfür kann eine der sechs Multiplikationsversionen angewendet werden, z.B.:

    REAL, DIMENSION (qmax, qmax) :: a, b, c

    c = 0
    DO ii = 1, qmax, nb
      DO kk = 1, qmax, nb
        DO jj = 1, qmax, nb
	  Cii,jj := Cii,jj + Aii,kk.Bkk,jj
        END DO
      END DO
    END DO
Für die Multiolikation Aii,kk.Bkk,jj kann davon unabhängig eine andere der sechs Varianten gewählt werden. Es gibt daher 36 geblockte Versionen der Matrizenmultiplikation.

Bei der Multiplikation von Blockmatrizen zeigt sich, daß die Anordnung der zwei äußeren Schleifen auf das Leistungsverhalten unerheblich ist. Dieses wird, wie bei den ungeblockten Varianten, im wesentlichen durch die innerste Schleife bestimmt.

ikj-jki-Variante

    REAL, DIMENSION (qmax, qmax) :: a, b, c

    c = 0
    DO ii = 1, qmax, nb
      DO kk = 1, qmax, nb
        DO jj = 1, qmax, nb
	  DO j = jj, MIN(qmax, jj+nb-1)
	    DO k = kk, MIN(qmax, kk+nb-1)
	      DO i = ii, MIN(qmax, ii+nb-1)
		c(i,j) = c(i,j) + a(i,k)*b(k,j)
	      END DO
	    END DO
	  END DO
	END DO
      END DO
    END DO
Die Leistungsdaten, die der nachfolgenden Abbildung zugrundeliegen, wurden mit der Blockgröße nb=128 ermittelt. Dieser Wert wurde der verwendeten Workstation angepasst und entspricht einem sehr flachen Optimum. Andere Blockungsfaktoren, im Bereich zwischen 80 und 256, liefern fast genauso gute Resultate.
Bild

5.5.4d Blockung mit Kopieren

Daten werden in Hilfsfelder umkopiert. Diese nehmen jeweils einen Block (eine Teilmatrix von A, B oder C) auf.
Idee: Alle Berechnungen werden mit den Hilfsfeldern durchgeführt, sodaß keine Cache-Konflikte auftreten und die Schrittweite beim Zugriff auf keinen Fall größer als die Zeilenzahl nb des Blocks ist. Ohne diese Hilfsfelder können bei geblockten Multiplikationsalgorithmen die Zugriffs-Schrittweiten im ungünstigsten Fall n sein.

Weiters kann bei der Verwendung von Hilfsfeldern der zu bearbeitende Block transponiert (h4) kopiert werden. Damit kann man bei den innersten Schleifen alle Zugriffsschrittweiten kleinstmöglich halten.

Das Kopieren eines Blockes bietet sich vor allem für die Matrix A an. Dadurch wird auch für die ijk-Variante - die sich durch eine geringe Zahl von Schreibzugriffen auszeichnet - eine sehr gute Datenlokalität erreicht.

ikj-jki-Variante

    REAL, DIMENSION (qmax, qmax) :: a, b, c
    REAL aa(nb, nb)                           ! Hilfsfeld             

    c = 0
    DO ii = 1, qmax, nb
      DO kk = 1, qmax, nb
        DO jj = 1, qmax, nb
          DO ib = ii,  MIN(qmax, ii+nb-1)
            aa(kb-kk+1,ib-ii+1) = a(ib,kb)    ! Kopieren und Transponieren
          END DO
        END DO
        DO jj = 1, qmax, nb
	  DO j = jj, MIN(qmax, jj+nb-1)
	    DO k = kk, MIN(qmax, kk+nb-1)
	      DO i = ii, MIN(qmax, ii+nb-1)
                c(i,j) = c(i,j) + aa(k-kk+1,i-ii+1)*b(k,j)
	      END DO
	    END DO
          END DO
	END DO
      END DO
    END DO
Für die ikj-ijk-Variante ergibt sich für größere Matrizen (m>700) eine wesentliche Leistungssteigerung. Da die Elemente von A nun spalten- statt zeilenweise dereferenziert werden. Bei den anderen Versionen sind dagegen kaum Leistungssteigerungen feststellbar. Die folgend dargestellten Kurven wurden wieder mit der maschinenspezifischen Blockgröße nb=128 erhalten.
Bild

5.5.4e Blockung mit Kopieren und Schleifenaufrollen

Größte Leistungssteigerungen lassen sich durch Kombination aller Techniken - Aufrollen von Schleifen, Blockung und Kopieren von Blöcken - erreichen.

Variante aus [Dongarra et al, 1990]

    REAL, DIMENSION (qmax, qmax) :: a, b, c
    REAL aa(nb, nb)                            ! Hilfsfeld
    REAL t11, t21, t12, t22

    c = 0
    DO kk = 1, qmax, nb
      kspan = MIN(nb, qmax-kk+1)
      DO ii = 1, qmax, nb
        ispan = MIN(nb, qmax-ii+1)
        ilen = 2*(ispan/2)                     ! auf Gerade abrunden
        DO i = ii, ii+ispan-1
          DO k = kk, kk+kspan-1
            aa(k-kk+1,i-ii+1) = a(i,k)         ! transponiertes Kopieren
          END DO
        END DO
        DO jj = 1, qmax, nb
          jspan = MIN(nb, qmax-jj+1)
          jlen = 2*(jspan/2)
          DO j = jj, jj+jlen-1, 2              ! einfach aufgerollt
            DO i = ii, ii+ilen-1, 2            ! einfach aufgerollt
              t11 = 0.
              t21 = 0.
              t12 = 0.
              t22 = 0.
              DO k=kk, kk+kspan-1
                t11 = t11 + aa(k-kk+1,i-ii+1)*b(k,j  )
                t21 = t21 + aa(k-kk+1,i-ii+2)*b(k,j  )
                t12 = t12 + aa(k-kk+1,i-ii+1)*b(k,j+1)
                t22 = t22 + aa(k-kk+1,i-ii+2)*b(k,j+1)
              END DO
              c(i  ,j  ) = c(i  ,j  )+t11
              c(i+1,j  ) = c(i+1,j  )+t21
              c(i  ,j+1) = c(i  ,j+1)+t12
              c(i+1,j+1) = c(i+1,j+1)+t22
            END DO
          END DO
        END DO
      END DO
    END DO
Die beiden äußeren Schleifen der inneren Schleifenschachtelung werden dabei einfach aufgerollt (j und i), und ein Block der Matrix A wird kopiert und dabei transponiert (h3).
Bild

5.5.5 Präcompiler und Optimierer

"It should be clear however, that with a sufficiently complex preprocessor, it is entirely possible to exactly emulate any compiled language. Compilers are exactly this kind of preprocessor." - Scott Nudds

Viele Programmtransformationen können auch von optimierenden Compilern automatisch durchgeführt werden. So steht z.B. auf HP-Workstations ein optimierender Präcompiler mit einer eigenen, BLAS-ähnlichen Vektor-Library zur Verfügung. Dieser erwähnte Präcompiler wandelt das bereits bekannte Beispiel

    REAL, DIMENSION (qmax, qmax) :: a, b, c

    c = 0
    DO i = 1, qmax, 1
      DO j = 1, qmax, 1
        DO k = 1, qmax, 1
          c(i,j) = c(i,j) + a(i,k)*b(k,j)
        END DO
      END DO
    END DO
in den Aufruf
    CALL blas_$sgemm('n','n',(m),(n),(k),0.,a(1,1),lda,b(1,1),ldb,1.,c(1,1),ldc)
um.
Bild
Das manuelle, sorgfältige Optimieren durch erfahrene Numeriker ist dem automatischen deutlich überlegen. Das aber bei der Multiplikation von größeren Matrizen dabei die vierfache Leistung zur automatisch-optimierten Version erzielt wurde, ist allerdings untypisch. Die Ursache hierfür ist die mangelhafte Implementierung von blas_$sgemm.

Die Auswahl geeigneter Alghorithmusvarianten und das Fine-Tuning erfordert einen beträchtlichen Aufwand. Sodaß in den meisten Fällen das zurügreifen auf bereits vorhandene Bibliotheken, wo dies möglich und ausreichend ist, vorzuziehen ist.

5.5.6 Anwendungsbeispiele von Bibliotheken

Nachfolgend ein Beispiel für die Verwendung der physikalische Berechnungen unterstützenden C++-Bibliothek RHALE++:

     void Decompose(const double delt, SymTensor& V,
                    Tesor& R, const Tesor& L)
     {
       Symtensor D = Sym(L);
       AntiTensor W = Anti(L);
       Vector z = Dual(V*D);
       Vector omega = Dual(W) - 2.0*Inverse(V-Tr(V)*One)*z;
       AntiTensor Omega = 0.5*Dual(omega);

       R = Inverse(One-0.5*delt*Omega) * (One+0.5*delt*Omega)*R;
       V += delt*Sym(L*V - V*Omega);
     }
[Budge, 1992] dazu: "Dieser Code ist verständlich und die zugrundeliegende Klassenbibliothek ist vielseitig und leicht zu pflegen. Ein Physiker, der mit dem Algorithmus der polaren Zerlegung vertraut ist, versteht diesen Programmteil ohne zusätzlich Dokumentation."

5.5.7 Zukünftige Entwicklung

Die hier gezeigte Optimierungsverfahren sind stark von der zugrundeliegenden Rechnerarchitektur abhängig. Bei Hardware ist aber die Entwicklung kaum vorhersehbar. Möglicherweise wird durch den nächsten Technologiesprung vieles hier gezeigte unbedeutend und taucht erst mit dem darauf folgenden Technologie wieder aus der Versenkung auf.

Welche Programmiersprache wird in Zukunft im Bereich der numerischen Berechnungen die Führung erhalten?
Nun, der Bereich der wissenschaftlichen und numerischen Berechnungen ist relativ klein, wenn man ihn nach der Anzahl der in dieser Richtung aktiven Programmierer beurteilt. Gleichzeitig haben Programme aus diesem Bereich für viele anderen Gebiete große Bedeutung.
Im Moment ist eine Entwicklung zu weiterentwickelten Alghorithmen zu bemerken, von der Sprachen nur dann profitieren können, wenn sie viele verschiedene Datenstrukturen effizient unterstützen.
Werden Sprachen die sich im technischen Bereich bewährt haben, wie C++ sich hier auch durchsetzen? Allerdings müßten hier die Compiler bessere Optimierungen für Probleme der numerische Mathematik erhalten.
Oder gelingt es einer neueren FORTRAN-Version flexiblere Konzepte und Paradigmen, wie Objektorientierung, Generizität, ... zu unterstüzen?
Derzeit scheinen sich beide Entwicklungen abzuzeichnen, sodaß man künftig mehr Auswahl an geeigneten Sprachen erwarten kann.

5.5.8 Zugehörige Seiten und interessante Links

Falls Sie bisher keine Links weiterverfolgt haben, können Sie nun ebenfalls zu den restlichen Seiten weitergehen.

Hiermit vermaschte Kapiteln: Interessantes im WWW:

5.5.9 Macht's gut, und danke für den Fisch

Gottes letzte Botschaft an die Menscheit: "Wir entschuldigen uns für die Strapazen."
- Macht's gut, und danke ... / Douglas Adams

Dies ist der aufbereitete Text zu [Überhuber, 1995] Abschnitt "6.7 Fallstudie: Matrizenmultiplikation". Da die Autorenschaft kleiner als die Leserschaft ist (=Spekulation :-), ist es im Sinne der Rationalität möglichst viel Knobelei und Denkarbeit in Richtung der Verfasser zu verschieben. Dieser Idee wurde hier versucht weiterhin zu folgen.


[ < ] [ globale Übersicht ] [ Kapitelübersicht ] [ Stichwortsuche ] [ > ]