CXpa

Durch das Vektorisieren sind Beschleunigungen bis zum Faktor 6 durchaus erreichbar. Der Vergleich der Zeiten für O1- und O2-Optimierung zeigt aber, daß wir davon noch weit entfernt sind. Es macht allerdings keinen Sinn, nun wild ''herumzuoptimieren'', denn was nützt eine 10-fache Beschleunigung einer Routine, die nur 1 Promille der Gesamt-Rechenzeit verbraucht? Der nächste Schritt besteht also darin, die Stellen im Programm herauszufinden, die den größten Teil der CPU-Zeit benötigen. Zu diesem Zweck dienen sogenannte Profiler, das sind Programme, die das Laufzeitverhalten einzelner Programmteile (Routinen, Schleifen, Blöcke) analysieren und tabellarisch zusammenstellen.
In diesem Abschnitt soll am Beispielprogramm linalg gezeigt werden, wie man mit dem Convex-Profiler CXpa (''ConveX Performance Analyzer'') arbeitet. Ausführlich wird der CXpa im Abschnitt 3.2 vorgestellt. Natürlich macht es für unser Beispiel nicht allzuviel Sinn, den Programm-Ablauf als Ganzes beschleunigen zu wollen. Stattdessen wollen wir versuchen herauszufinden, welche Routinen schlecht vektorisieren, um im nächsten Abschnitt einige einfache Optimierungen auszuprobieren. Dazu vergleichen wir die Laufzeit-Daten für eine O1- und eine O2-optimierte Version miteinander.
Um den CXpa einzusetzen, muß man zunächst mit der Option -pa übersetzen und linken, d.h. diese Option muß im Makefile bei FFLAGS und LDFLAGS eingetragen werden. Nach dem Neu-Übersetzen wird der Profiler mit ''cpa linalg'' aufgerufen. Daraufhin wird der Bildschirm neu aufgebaut, und es erscheint (neben Startinformationen) der Prompt ''(cpa)'', an dem CXpa-Kommandos eingegeben werden können. Nun muß man angeben, welche Informationen i.f. gesammelt werden sollen. Mit dem Kommando
        monitor routine all
werden dazu alle Routinen zum ''Profilen'' ausgewählt. Als nächstes wird mit dem Kommando ''run'' das Programm unter CXpa-Kontrolle gestartet; dabei werden Laufzeit-Informationen in ein File namens ''cpa.pdf'' geschrieben. Mit dem Befehl ''analyze routine'' wird dieses File ausgewertet und einige Tabellen ausgegeben, von denen uns hier nur der Beginn der ersten interessiert:

Routine Performance Analysis .     
(sorted by CPU time (less children)) .     
  CPU Time .CPU Time Times .Routine    
  (less children) .(plus children) Called .Name    
  0 .756 42 .8% 0 .756 42 .8% 1 gaussj
  0 .575 32 .6% 0 .575 32 .6% 2 testmt
  0 .226 12 .8% 0 .226 12 .8% 101 lubksb
  0 .119 6 .7% 0 .119 6 .7% 1 ludcmp
  0 .037 2 .1% 0 .037 2 .1% 10100 drand_$n
  0 .034 1 .9% 0 .070 4 .0% 1 getmat
  6 .362m 0 .4% 6 .362m 0 .4% 2 ernorm
  5 .420m 0 .3% 5 .420m 0 .3% 2 setmat
  2 .266m 0 .1% 0 .225 12 .8% 1 luinv
  0 .334m 0 .0% 0 .692m 0 .0% 1 getvec
  0 .147m 0 .0% 0 .225m 0 .0% 2 _findenv
  0 .136m 0 .0% 1 .763 99 .9% 1 main
  0 .133m 0 .0% 0 .146m 0 .0% 4 lwrt_A
  0 .098m 0 .0% 0 .235m 0 .0% 1 f_exit
  0 .092m 0 .0% 0 .316m 0 .0% 4 fwrite
  0 .078m 0 .0% 0 .078m 0 .0% 38 strncmp
  0 .075m 0 .0% 0 .361m 0 .0% 3 ini_std
    .    .    .    .  .     .
    .    .    .    .  .     .
  0 .059m 0 .0% 0 .059m 0 .0% 2 setvec


Zu jeder Routine - auch solchen, die aus der Fortran-Standard-Bibliothek stammen - ist hier angegeben, wieviel CPU-Zeit in der Routine selbst verbraucht wurde, und zwar absolut (in Sekunden oder, durch ''m'' markiert, Millisekunden) und relativ zur gesamten CPU-Zeit, danach, wieviel CPU-Zeit in der Routine und allen von ihr aufgerufenen Unterroutinen verbraucht wurde (wieder absolut und relativ) und schließlich, wie oft die Routine insgesamt aufgerufen wurde.
Wir beenden den CXpa durch ''quit'' und wiederholen die ganze Prozedur mit der Optimierungsstufe O2. Da bereits ein File cpa.pdf existiert, werden wir beim ''run'' gefragt, ob wir dieses File überschreiben wollen. Mit ''y'' bejahen wir und erhalten nach dem analyze-Schritt folgende Tabelle:

Routine Performance Analysis .     
(sorted by CPU time (less children)) .     
  CPU Time .CPU Time Times .Routine Name    
  (less children) .(plus children) Called .Name    
  0 .442 57 .2% 0 .442 57 .2% 1 gaussj
  0 .133 17 .1% 0 .133 17 .2% 2 testmt
  0 .079 10 .2% 0 .079 10 .2% 101 lubksb
  0 .048 6 .3% 0 .048 6 .3% 1 ludcmp
  0 .037 4 .8% 0 .037 4 .8% 10100 drand_$n
  0 .028 3 .6% 0 .065 8 .4% 1 getmat
  1 .388m 0 .2% 1 .388m 0 .2% 2 ernorm
  0 .889m 0 .1% 0 .889m 0 .1% 2 setmat
  0 .769m 0 .1% 0 .078 10 .1% 1 luinv
  0 .273m 0 .0% 0 .644m 0 .1% 1 getvec
  0 .137m 0 .0% 0 .213m 0 .0% 2 _findenv
  0 .134m 0 .0% 0 .145m 0 .0% 4 lwrt_A
  0 .132m 0 .0% 0 .772 99 .9% 1 main
  0 .100m 0 .0% 0 .243m 0 .0% 1 f_exit
  0 .094m 0 .0% 0 .323m 0 .0% 4 fwrite
  0 .077m 0 .0% 0 .383m 0 .0% 3 ini_std
  0 .076m 0 .0% 0 .076m 0 .0% 38 strncmp
    .    .    .    .  .     .
    .    .    .    .  .     .
  0 .016m 0 .0% 0 .016m 0 .0% 2 setvec


Wir berechnen aus den 1. Spalten der beiden Tabellen die ''Vektorisierungs-Beschleunigung'' B $=$ cpu(O1)/cpu(O2) und erhalten:

  CPU Time CPU Time .B .    . 
  O1 O2 .  .    . 
setmat 5 .420m 0 .889m 6 .1   . 
ernorm 6 .362m 1 .388m 4 .6   . 
testmt 0 .575 0 .133 4 .3   . 
setvec 0 .059m 0 .016m 3 .7   . 
luinv 2 .266m 0 .769m 2 .9   . 
lubksb 0 .226 0 .079 2 .9   . 
ludcmp 0 .119 0 .048 2 .5   . 
gaussj 0 .756 0 .442 1 .7   . 
getmat 0 .034 0 .028 1 .2   . 
getvec 0 .334m 0 .273m 1 .2   . 
Legt man den Wert 6.0 als (nahezu) optimal zugrunde, dann kann man die Routinen nach ihrem Vektorisierungsgrad grob folgendermaßen einteilen:

testmt, ernorm, setvec, setmat sehr gut
ludcmp, lubksb, luinv mittelmäßig
gaussj schlecht
getvec, getmat gar nicht


Angesichts der Anteile der Routinen an der CPU-Zeit würde man sich normalerweise als nächstes der Routine gaussj als geeignetem Kandidaten für weitere Optimierungsversuche zuwenden.

previous    contents     next

Peter Junglas 18.10.1993