19. Optimering

19.0 Inledning

Optimering av Fortranprogram sker numera oftast helt automatiskt av kompilatorn. I detta kapitel skall vi gå igenom ett par speciella optimeringsegenskaper, som det kan vara bra att känna till.

En bra bok om Fortran-optimering i allmänhet är den av Metcalf, medan optimering främst på superdatorer behandlas av Levesque och Williamson. En nyare bok på samma tema är den av Goedecker och Hoisie.

I sin enklaste form består optimering av att finna gemensamma deluttryck, beräkna dessa samt undvika att upprepa beräkningen, till exempel genom att ta ut beräkningen ur DO-slingor och spara resultatet för användning inuti.

Ett krav på optimering är att det matematiska resultatet ej ändras, däremot kan det numeriska resultatet komma att ändras på grund av att avrundningsfelet hos flyttalsberäkningen blir något annorlunda. I vissa fall skulle till och med allvarliga konsekvenser kunna uppträda genom att spill ("overflow") eller bottning ("underflow") uppkommer som en följd av ändrad beräkningsordning. Ett teoretiskt exempel är de matematiskt ekvivalenta uttrycken (a*b)/c och a*(b/c), men eftersom parenteser skall respekteras, så är dessa båda uttryck inte ekvivalenta i Fortran-mening. Det är dessutom föreskrivet att all beräkning (av samma prioritet) skall ske från vänster, utom i exponenter (efter **, jämför prioritetsreglerna i sektion 3.15).

En annan teknik för optimering är att, eftersom funktions- och subrutinanrop är ganska kostsamma, flytta in respekive kod direkt i den anropande rutinen, så kallad "inlining". Detta rekommenderas endast om respektive subrutin är "liten" i lämplig mening. Man kan således skriva ett strukturerat program med många små funktioner och subrutiner och överlåta åt kompilatorn att framställa en effektiv version genom "inlining".

Mycket arbete läggs ner på att optimera slingor, speciellt då DO-slingor. Speciella problem (och möjligheter) uppstår vid vektormaskiner och/eller parallella maskiner. Att optimera slingor (även multipel-slingor) för vektormaskiner har nu blivit en etablerad teknik.

19.1 Slingor

Det vanligaste tricket för att snabba upp exekveringen av en multipel DO-slinga är att se till att indexen varieras i optimal ordning, normalt så att den inre slingan är den där även fältets index varierar snabbast.

Jag har testat ett enkelt exempel slinga3.f90 på DEC Alfa utnyttjande Digitals Fortran med de olika optimeringsgraderna O0 till O5.

Resultat blev följande, efter några enkla redigeringar. Här är metod 1 en explict slinga där sista index varierar snabbast, metod 2 en explict slinga där första index varierar snabbast, samt metod 3 en fält-operation.

Script started on Fri Sep 10 13.14.33 1999
[Setting up eno.mai.liu.se (OSF1-V4.0-alpha) done.]

eno[~/tana70]> f90 -O0 slinga3.f90
 Tid i metod 1 =   0.0566 sekunder
 Tid i metod 2 =   0.0400 sekunder
 Tid i metod 3 =   0.0176 sekunder
eno[~/tana70]> f90 -O1 slinga3.f90
 Tid i metod 1 =   0.0410 sekunder
 Tid i metod 2 =   0.0137 sekunder
 Tid i metod 3 =   0.0107 sekunder
eno[~/tana70]> f90 -O2 slinga3.f90
 Tid i metod 1 =   0.0380 sekunder
 Tid i metod 2 =   0.0117 sekunder
 Tid i metod 3 =   0.0117 sekunder
eno[~/tana70]> f90 -O3 slinga3.f90
 Tid i metod 1 =   0.0341 sekunder
 Tid i metod 2 =   0.0078 sekunder
 Tid i metod 3 =   0.0088 sekunder
eno[~/tana70]> f90 -O4 slinga3.f90 (default)
 Tid i metod 1 =   0.0341 sekunder
 Tid i metod 2 =   0.0078 sekunder
 Tid i metod 3 =   0.0088 sekunder
eno[~/tana70]> f90 -O5 slinga3.f90
 Tid i metod 1 =   0.0078 sekunder
 Tid i metod 2 =   0.0078 sekunder
 Tid i metod 3 =   0.0079 sekunder
Vi ser således att i detta fall förefaller det ha stor betydelse om första (metod 2, bäst) eller sista (metod 1, sämst) index varierar snabbast, medan man vid en fältoperation får en betydande ytterligare uppsnabbning vid låg optimering. Utmatningen av summan av alla elementen sker i syfte att lura optimeringen, i annat fall kan en kraftfull optimering undvika att räkna ut något överhuvudtaget.

Utnyttjande NAG:s nya Fortran 95 kompilator på en Sun SPARC station 10 erhölls motsvarande resultat.

Jag har upprepat körningen på en Cray C90 och då använt DIM = 100 i stället för DIM = 50.

Script started on Fri Sep 10 13:33:47 1999
sn4004 1 % f90 -V
Cray CF90 Version 2.0.1.0 07/19/99 15:29:15
sn4004 3 % f90 -O0 slinga3.f90
sn4004 4 % a.out
  Tid i metod 1 =  1.240 sekunder
  Tid i metod 2 =  1.293  sekunder
  Tid i metod 3 =  0.019  sekunder
sn4004 6 % f90 -O1 slinga3.f90
sn4004 7 % a.out
  Tid i metod 1 =  0.016  sekunder
  Tid i metod 2 =  0.004  sekunder
  Tid i metod 3 =  0.004  sekunder
sn4004 8 % f90 -O2 slinga3.f90     (default)
sn4004 9 % a.out
  Tid i metod 1 =  0.002  sekunder
  Tid i metod 2 =  0.002  sekunder
  Tid i metod 3 =  0.002  sekunder
sn4004 10 % f90 -O3 slinga3.f90    (inkl. autotasking)
sn4004 11 % a.out
  Tid i metod 1 =  0.050  sekunder
  Tid i metod 2 =  0.002  sekunder
  Tid i metod 3 =  0.002  sekunder

Vid låg optimering är metod 3 den klart bästa, vid måttlig optimering blir även metod 2 bra. Vid autotasking blir metod 1 extra ineffektiv jämfört med de andra metoderna.

Jag har nu valt ett annat exempel på Cray C90, nämligen där kompilatorn har problem med att vektorisera den slinga som ligger innerst. I exemplet chanel1.f90 sker i den första trippelslingan en initiering och i den andra en beräkning, vilken även tidmätes med Crays funktion SECOND(). I den innersta beräkningsslingan finns ett beroende (elementet i position k beror av det i position k-1), vilket medför att denna ej kan vektoriseras utan vidare.

Jag har valt att göra körningen med optimeringsgrad 1. När jag i stället kör chanel2.f90 med samma optimeringsgrad sker exekveringen mycket snabbare. Detta beror på att den innersta slingan nu är över index j och det finns inte längre något beroende som förhindrar vektorisering. Med högre optimeringsgrad klarar kompilatorn av problemet på egen hand.

Script started on Tue Jul 20 08:44:52 1999
sn4004 3 % f90 -V -O1 chanel1.f90
Cray CF90 Version 2.0.1.0 07/20/99 08:46:02
cf90: COMPILE TIME 1.588000 SECONDS
cf90: MAXIMUM FIELD LENGTH 238659 DECIMAL WORDS
cf90: 26 SOURCE LINES
cf90: 0 ERRORS, 0 WARNINGS, 0 OTHER MESSAGES, 0 ANSI
cf90: CODE: 97 WORDS, DATA: 180 WORDS
sn4004 4 % a.out
 Normal beraekningsordning tar  0.147152 sekunder.
 -2.320756189087945E+323
sn4004 5 % f90 -V -O1 chanel2.f90
Cray CF90 Version 2.0.1.0 07/20/99 08:46:38
sn4004 6 % a.out
 Optimerad beraekningsordning tar  0.008127 sekunder.
 -2.320756189087945E+323
sn4004 7 % 

Exekveringen av en DO-slinga kan ibland snabbas upp genom att dela upp den ("unrolling") så att systemets parallellism kan utnyttjas maximalt. Detta gäller framför allt på superdatorer. På T3E finns de speciella optimeringsalternativen unroll0, unroll1 och unroll2, samt direktiven !DIR$ UNROLL och !DIR$ NOUNROLL.

Anm. Cray kallar operativsystemet på C90 för UNICOS och det på T3E för unicos/mk.

En utmärkt genomgång av "unrolling" ges på sid 138-142 i boken av Levesque och Williamson.

19.2 Utmatning av hela fält

Vid utmatning av fält gäller att denna sker mycket snabbare om "hela fältet" kan matas ut direkt, utan att gå via en DO-slinga, jämför nedanstående exempel.
      REAL, DIMENSION (1:10000) :: A
! Metod 1, explicit DO-slinga
      DO I = 1, 10000
         WRITE(1) A(I)
      END DO
! Metod 2, implicit DO-slinga
      WRITE(1) (A(I), I = 1, 10000)
! Metod 3, implicit 
      WRITE(1) A
Det visar sig här att metod 3 är mycket snabbare, även i ren CPU-tid. Jag har här ett fullständigt körbart exempel slinga1.f90 på detta. Här följer utmatningen från programmet samt storleken på de erhållna resultatfilerna.
Script started on Fri Jul  9 11:25:57 1999
On a Sun SPARCstation 10 with the NAGWare f90 compiler
Version 2.2 (261)
%./a.out
  Tid i metod 1 =    0.240  sekunder
  Tid i metod 2 =    0.040  sekunder
  Tid i metod 3 =    0.020  sekunder
%/bin/ls -slFag 
 128 -rw-r--r--  1 boein    nsc  120000 Jul  9 11:26 slask1
  40 -rw-r--r--  1 boein    nsc   40008 Jul  9 11:26 slask2
  40 -rw-r--r--  1 boein    nsc   40008 Jul  9 11:26 slask3
Vi ser här att metod 1 (explicit DO-slinga) både är långsammare och genererar en större fil.

Nu följer resultatet från DEC Alpha där jag dock ökat antalet element DIM från 10 000 till 100 000. På Sun blev det problem med en buffer vid för många element.

Script started on fre jul  9 12.24.50 1999
[Setting up eno.mai.liu.se (OSF1-V4.0-alpha) done.]
On a DEC Alpha station with the DIGITAL Fortran 90 compiler 
Version 5.0-492

eno[~/tana70]> a.out
  Tid i metod 1 =   3.125   sekunder
  Tid i metod 2 =   0.694   sekunder
  Tid i metod 3 =   0.695   sekunder
eno[~/tana70]> /bin/ls -slFag
1264 -rw-r--r--   1 boein    num  1200000 Jul  9 12:26 slask1
 448 -rw-r--r--   1 boein    num   400008 Jul  9 12:26 slask2
 448 -rw-r--r--   1 boein    num   400008 Jul  9 12:26 slask3

Nu följer resultatet från CRAY C90 där jag även där har ökat antalet element DIM från 10 000 till 100 000. Filerna blir dessutom större eftersom precisionen på den maskinen är högre.

Script started on Fri Jul  9 12:59:06 1999
On a Cray C90 with Cray CF90 Version 2.0.1.0

sn4004 1 % a.out
  Tid i metod 1 =  2.457419  sekunder
  Tid i metod 2 =  0.002330  sekunder
  Tid i metod 3 =  0.002134  sekunder
sn4004 2 % ls -slFag 
 3136 -rw-------   1 g97900   1605632 Jul  9 12:59 slask1
 1568 -rw-------   1 g97900    802816 Jul  9 12:59 slask2
 1568 -rw-------   1 g97900    802816 Jul  9 12:59 slask3

19.3 Utmatning av fält formatterat eller oformatterat?

Det visar sig inte helt förvånande att oformatterad utmatning både är snabbare och normalt ger mindre filer än formatterad utmatning. Notera även att den oformatterade utmatningen inte ger någon noggrannhetsförlust.

Jag har här ett fullständigt körbart exempel slinga2.f90 på detta. Här följer utmatningen från programmet samt storleken på de erhållna resultatfilerna.

Script started on Fri Jul  9 14:40:24 1999
On a Sun SPARCstation 10 with the NAGWare f90 compiler 
Version 2.2 (261)
hugo 3 % a.out
  Tid i metod 1 =    2.240  sekunder (ett tal per rad)
  Tid i metod 2 =    1.980  sekunder (tio tal per rad)
  Tid i metod 3 =    0.020  sekunder (oformatterat)
hugo 4 % /bin/ls -slFag 
 152 -rw-r--r--  1 boein    nsc  140000 Jul  9 14:40 slask1
 136 -rw-r--r--  1 boein    nsc  131000 Jul  9 14:40 slask2
  40 -rw-r--r--  1 boein    nsc   40008 Jul  9 14:40 slask3

Kompilatorer Innehåll Nyheterna i Fortran 95


Senast modifierad: 5 juli 2001
boein@nsc.liu.se