home *** CD-ROM | disk | FTP | other *** search
/ Turbo Toolbox / Turbo_Toolbox.iso / spezial / 02 / texte / pascomp.7 < prev    next >
Encoding:
Text File  |  1980-01-03  |  15.4 KB  |  369 lines

  1.  
  2.  
  3.  
  4. PASCOMP
  5.  
  6. PASCAL baut einen Pascal-Compiler
  7.  
  8. Teil 8: Optimierung
  9.    von  Johannes Velmans
  10.  
  11. Nachdem  wir die Synthesephase eines Compilers mit dem Thema "Co-
  12. deerzeugung"  eingeleitet  haben,  fahren wir nun mit dem Kapitel
  13. Optimierung  fort.  Was  ist eigentlich das Ziel der Optimierung?
  14. Natürlich  eine  Verbesserung  des  Programmlaufes unter diversen
  15. Gesichtspunkten,  wie zum Beispiel Speicherplatzbedarf, Register-
  16. bedarf, Ausführungszeit, Codegröße und vieles mehr.
  17.  
  18. Dabei ist jedoch zu beachten, daß ein absolutes Optimum bezüglich
  19. aller  genannten  Gesichtspunkte  nicht  realisierbar ist, da die
  20. meisten  Optimierungstechniken in einer derartigen Wechselwirkung
  21. zueinander  stehen, daß im Grunde genommen der vollständige Opti-
  22. mierungsprozeß  so  lange  in seiner Gesamtheit wiederholt werden
  23. müßte, bis sich zu guter Letzt keine Änderungen mehr ergeben.
  24.  
  25. Da dieses in der Praxis wegen des zu großen Rechenaufwandes nicht
  26. durchführbar  ist,  beschränken wir uns auf eine feste Anzahl und
  27. Reihenfolge  von  Verfahren, um insgesamt eine relativ gute Opti-
  28. mierung zu erreichen. Unser Hauptaugenmerk ist nun darauf gerich-
  29. tet,  die  durch  die Eigenschaften der Programmiersprache selbst
  30. zwangsläufig  entstehenden Probleme in Bezug auf Effizient so gut
  31. es  geht zu komprimieren und somit eine Verbesserung herbeizufüh-
  32. ren  -  und  nicht  Probleme, die durch schlechten Progammierstil
  33. entstanden sind, zu beseitigen.
  34.  
  35. Die  Grundlage  einer jeden Optimierung bilden sogenannte Kosten-
  36. Funktionen.  Die  allgemein  üblichen Bewertungskriterien solcher
  37. Kosten  sind zum Beispiel die Code-Größe, die Ausführungszeit und
  38. der  Speicherplatzbedarf  von Daten. Aber auch eine Menge von Be-
  39. ziehungen zwischen den einzelnen Komponenten eines Programms sind
  40. hier  von Bedeutung. Gute Optimierung hat zwangsläufig die Imple-
  41. mentierung  verschiedener Datenzugriffsoperationen zur Folge. Man
  42. kann sogar weitergehend sagen, daß unterschiedliche Optimierungs-
  43. formen  auch unterschiedliche Ausführungsreihenfolgen der verwen-
  44. deten  Optimierungsschritte  zur  Folge  haben. Auf die einzelnen
  45. Optimierungsschritte gehen wir später noch ein.
  46.  
  47. Aufgrund  dieser  unterschiedlichen  Ausführungsreihenfolgen  der
  48. Optimierungsschritte  stellt  der in der semantischen Analyse er-
  49. zeugte  attributierte  Strukturbaum keinen Ausgangspunkt für eine
  50. Optimierung  dar. Der Grund dafür: Der attributierte Strukturbaum
  51. spiegelt in erster Linie die verwendete Syntax und Semantik eines
  52. Programms wieder. Für die Optimierung ist er viel zu umfangreich,
  53. da  hier  hauptsächlich  nur  der Berechnungsablauf von Bedeutung
  54. ist. Zu diesem Zweck wird jede für die Optimierung nicht benötig-
  55. te  Information  aus dem Strukturbaum gestrichen. Eine weitere zu
  56. umgehende  Schwierigkeit  bei  der Optimierung besteht darin, daß
  57. bei  vielen Maschinen eine Reihe von Berechnungsoperationen nicht
  58. in Form einer einzigen Berechnung dargestellt werden können, son-
  59. dern ein "Anweisungspaket" erfordern. Ein Beispiel hierfür stellt
  60. die  Addition  von  Real-Zahlen auf dem M68000-Prozessor dar, bei
  61. dem  für  eine derartige Addition mehrere Befehle notwendig sind.
  62. Eine  Einflußnahme  der  Optimierung auf solche Pakete ist erfah-
  63. rungsgemäß recht schwierig. Abhilfe schafft hier eine Bibliothek,
  64. in  der  solche  Routinen untergebracht werden. Das entsprechende
  65. Code-Stück wird dann einfach durch einen Verweis in diese Biblio-
  66. thek ersetzt.
  67.  
  68. Der aus dem attributierten Strukturbaum durch Weglassen verschie-
  69. dener  Teile  entstehende Baum wird als Berechnungsgraph bezeich-
  70. net.  Er stellt eine Schnittstelle zwischen der semantischen Ana-
  71. lyse und der Code-Optimierung bzw. Code-Erzeugung dar. Dieses von
  72. uns  nun  neu eingeführte Hilfsmittel soll folgende Eigenschaften
  73. besitzen:
  74.  
  75. - Alle Operationen des Quelltextes sind durch Operationssequenzen
  76. ersetzt worden, die dem Befehlssatz der Zielmaschine entsprechen.
  77. Eventuelle  Anpassungen werden jedoch nur dann mitberücksichtigt,
  78. wenn sie auch ihre Entsprechung in dem zugehörigen Code finden.
  79.  
  80.  
  81.  -Jede  Operation steht für sich alleine mit einer geeigneten An-
  82. zahl von Operanden. (Also kein "Versteckspielen" in Anweisungspa-
  83. keten mehr.)
  84.  
  85. -Adreßrechnungen sind explizit.
  86.  
  87. -Zuweisungen an Programmvariable werden getrennt von anderen Ope-
  88. rationen behandelt.
  89.  
  90. -Kontrollfluß wird mit Hilfe von bedingten und unbedingten Sprün-
  91. gen dargestellt.
  92.  
  93. Obwohl  die  Grundlage unseres Berechnungsgraphen der Befehlssatz
  94. der Zielmaschine ist, kann man hier doch von Maschinenunabhängig-
  95. keit  sprechen, da das Grundkonzept dieses Berechnungsgraphen eng
  96. an die Arbeitsweise eines Von-Neumann-Rechners angelehnt ist, der
  97. in seiner Grundstruktur die Ausgangsbasis vieler realer Maschinen
  98. bildet.
  99. Darstellen  läßt sich nun dieser Berechnungsgraph durch eine Ver-
  100. kettung von Knoten. In Pascal zum Beispiel haben derartige Knoten
  101. das  definierte  Aussehen, wie in Tabelle 1 gezeigt. Um uns einen
  102. Einblick  in  den Aufbau eines Berechnungsgraphen zu verschaffen,
  103. betrachten  wir  den  Berechnungsgraphen für das folgende Pascal-
  104. Programmstück:
  105.  
  106.  .....
  107.  (* der Speicherumfang für INTEGER sei 2
  108.    Bytes, der für REAL 4 Bytes *)
  109. VAR i,a : INTEGER;
  110. x : REAL;
  111.  .....
  112.   a := i;
  113.   WHILE a < 10 DO BEGIN
  114. a := a+1;
  115. x := a;
  116.   END;
  117.  
  118.  
  119. Betrachtet  man nun diesen Graphen, so läßt sich abschließend als
  120. Faustregel zu seiner Konstruktion sagen:
  121.  
  122. -nimm den attributierten Strukturbaum
  123.  
  124. -vereinfache seine Struktur
  125.  
  126. -lasse die deklarativen Teile weg
  127.  
  128. -vergiß alle nicht benötigten Attribute
  129.  
  130. -füge die Ausführungsreihenfolge neu hinzu.
  131.  
  132. Eine  weitere  notwendige Voraussetzung zur Optimierung ist neben
  133. der  Erstellung  des  oben  erwähnten Berechnungsgraphen auch die
  134. Kenntnis  über  gewisse Eigenschaften realer Maschinen, wobei die
  135. hier  angegebenen  Beispiele  sich  auf den Mikroprozessor M68000
  136. beziehen.
  137.  
  138. Zunächst  ein  paar  Sätze  zu Speicherklassen: Bei diesen unter-
  139. scheidet  man  zwischen  Hauptspeicher, Register, Spezialregister
  140. und  Registerkeller.  Der Hauptspeicher besteht aus direkt adres-
  141. sierbaren  Speicherzellen.  Je  nachdem, ob Gruppen von ein, zwei
  142. oder vier Bytes zusammengefaßt werden, spricht man von einer Ein-
  143. teilung in Byte (oder Halbwort), Wort oder Langwort. Entsprechend
  144. heißt  ein  Hauptspeicher  Byte-orientiert,  wenn  seine kleinste
  145. adressierbare  Zelle  ein Byte um faßt und Wort-orientiert, falls
  146. die  kleinste  adressierbare Einheit der Größe von zwei oder vier
  147. Bytes entspricht. Verwendet wird der Hauptspeicher als Programm-,
  148. Daten- oder Kellerspeicher.
  149.  
  150. Bei  Registern  handelt es sich um Speicher für Befehlsoperanden.
  151. Sie  werden  in allgemeine Register aufgeteilt: Datenregister für
  152. Ganzzahl-Operanden  (D0  bis D7), Register für Gleitpunkt-Operan-
  153. den,  Indexregister zur Verknüpfung mit Adressen und den Adreßre-
  154. gistern  (A0  bis  A7). Spezialregister umfassen zum Beispiel den
  155. Befehlszähler,  den  Condition-Code  (er enthält das Ergebnis von
  156. Vergleichen) und den Kellerpegel (A7).
  157. Je  nach  Anzahl der Operanden in Maschinenbefehlen unterscheidet
  158. man  zwischen  0-,  1-, 2- und 3-Adressbefehlen. Allgemein lassen
  159. sie sich in folgender Form schreiben:
  160.  
  161. 3-Adressbefehl :  A3 := A1 op A2
  162. 2-Adressbefehl :  A1 := A1 op A2
  163. 1-Adressbefehl :  Akkumulator := Akkumulator op A1
  164. 0-Adressbefehl :  op
  165.  
  166. Betrachtet  man nun die Operanden an sich, so läßt sich auch hier
  167. eine  Einteilung  vornehmen, die als Grundlage für die jeweiligen
  168. Adressierungsarten dient:
  169.  
  170. c konstanter Wert im Befehl
  171. Ri Registerinhalt
  172. Ri + c Registerinhalt verknüpft mit einem konstanten Wert
  173. Ri + Rj Verknüpfung zweier Registerinhalte
  174. -Ri Prädekrement
  175. Ri+ Postinkrement
  176.  
  177.  
  178. Unterteilungskri-
  179. terium               Knotenname    zugehörige Operanden
  180.  
  181. Ausdrücke             Operator     lg t-lop t_rop          t_seq
  182.                       Literal      lg  wert                t_seq
  183.                       Variable     lg  adresse             t_seq
  184.  
  185. Anweisungen           Zuweisung    lg t_ziel   t_quelle    t_seq
  186.                       if1             t_bed    t_then      t_seq
  187.                       if2             t_bed    t_then_else t_seq
  188.                       while           t_bed    t_rumpf     t_seq
  189.                       sprung          t_ziel               t_seq
  190.  
  191. Erläuterung:
  192. - t_ steht für Referenzen auf Nummern von entsprechenden Knoten
  193. - t?seq bezeichnet den nächsten Knoten in der
  194. Ausführungsreihenfolge.
  195. - stellt die Speichergröße des bezeichneten Knotennames dar.
  196. Tabelle 1: Verkettung von Knoten in Pascal
  197.  
  198.  
  199. Derartige  Kenntnisse  sind dann sehr hilfreich, wenn es zum Bei-
  200. spiel  um eine sinnvolle und registersparende Auswertung arithme-
  201. tischer  Ausdrücke  geht.  Dies soll hier näher anhand eines Bei-
  202. spiels erläutert werden. Nehmen wir folgenden arithmetischen Aus-
  203. druck:
  204.  
  205.   (a + b) * c +((a + b) c-(a + b))
  206.  
  207. Stellt  man  diesen  Ausdruck  in Baumform dar, aus dem sich dann
  208. sehr  einfach  eine  3-Adress-Form konstruieren läßt, um dann an-
  209. schließend  die entsprechenden Register zuzuteilen, so erhält man
  210. einen Ausdrucksbaum mit einer möglichen Registerzuteilung.
  211.  
  212.  
  213. Der zugehörige Code hätte dann die folgende Gestalt:
  214.  
  215. move a,D1         add D3,D2
  216. move b,D2         sub D2,D1
  217. add  D2,D1        move a,D2
  218. move c,D2         move b,D3
  219. mul  D2,D1        add D3,D2
  220. move a,D2         add D2,D1
  221. move b,D3
  222.  
  223. Insgesamt also ein ziemlich langes Codestück für diesen Ausdruck.
  224. Deshalb  erhebt  sich  die Frage, ob sich dieses Code-Stück nicht
  225. verkürzen  läßt. Schreiben wir uns noch einmal zur besseren Über-
  226. sicht den Ausdruck auf und betrachten ihn näher:
  227.  
  228.  (a+ b) * + c +((a + b) * c - (a + b))
  229.  
  230. Was wiederholt vorkommt, ist zum einen der Teilausdruck (a+b) und
  231. zum anderen der Teilausdruck (a+b)*c. Berücksichtigt man dies bei
  232. der  Konstruktion  des Baumes.
  233. Wird  Register  D3  eingespart. Aber auch das entsprechende Code-
  234. Stück wurde kürzer:
  235.  
  236. move a,D1
  237. move b,D2
  238. add D2,D1
  239. move c,D2
  240. mul D1,D2
  241. sub D2,D1
  242. add D2,D1
  243.  
  244.  
  245.  
  246. Lokale Optimierung
  247. Der  Vollständigkeit  halber  sei  hier erwähnt, daß es neben der
  248. lokalen  Optimierung  auch  eine  globale Optimierung gibt, deren
  249. nähere Ausführung jedoch den Rahmen dieses Artikels sprengen wür-
  250. de.
  251.  
  252. Die lokale Optimierung ist das einfachste Mittel, um eine Verbes-
  253. serung des ausführbaren Codes zu erreichen. Die Grundidee besteht
  254. darin,  sogenannte Basisblöcke zu definieren und jeden derartigen
  255. Basisblock  als  eine  von seiner Umgebung losgelösten Einheit zu
  256. betrachten und ihn völlig unabhängig von seiner Umgebung zu opti-
  257. mieren.  Dabei  definiert  man einen Basisblock als eine maximale
  258. Folge  von Operationen, so daß jede Operation genau einmal ausge-
  259. führt  wird,  wenn die erste ausgeführt wird.
  260.  
  261. Für jeden einzelnen Basisblock wird ein Berechnungsgraph konstru-
  262. iert,  transformiert und anschließend als Grundlage zur Konstruk-
  263. tion  des  endgültigen Maschinencodes benutzt. Um nun die jeweils
  264. einzelnen Basisblöcke zu optimieren, verwendet man folgende Stra-
  265. tegie in der angegebenen Reihenfolge:
  266.  
  267. 1. Schritt:
  268. Hier  wird der betrachtete Block mit Hilfe symbolischer Werte ab-
  269. gearbeitet. Das Ziel ist, überflüssige Berechnungen zu entdecken,
  270. um  diese  dann anschließend zu eliminieren. Beispiel einer über-
  271. flüssigen Anweisung:
  272.  
  273.   :              move b,D1
  274.   :              add c,D1
  275.   a := b+c       move D1,a
  276.   x := a-d       move a,D1
  277.                  ^ überflüssige Anweisung, kann eliminiert werden
  278.   :              sub d,D1
  279.   :              move D1,x
  280.  
  281.  
  282.  
  283. Hier  wird an den Grenzen zusammengesetzter Code-Stücke deutlich,
  284. daß überflüssige Anweisungen vorkommenmen können, die man dann im
  285. Laufe des Optimierungsprozesses entfernen kann. Ein weiteres Bei-
  286. spiel ist das Auffinden von derartigen Code-Stücken, die aufgrund
  287. der sie umgebenden Anweisungen eigentlich nie erreicht und ausge-
  288. führt  werden  können  - und somit auch als toter Code bezeichnet
  289. werden. Beispiel eines "toten" Code-Stückes: :
  290.  
  291.   :
  292.   goto m
  293.   ?
  294.   : Z- toter Code, dieser Teil kann
  295.   : niemals durchlaufen werden und
  296.   : kann daher bei der Optimierung
  297.   - entfernt werden
  298.   l:...
  299.   m:...
  300.   :
  301.  
  302. Wie  findet  man  nun  derartige überflüssige Berechnungen, toten
  303. Code  oder  andere überflüssige und somit zu eliminierende Anwei-
  304. sungen?  Dazu  erstellt  man aus dem gegebenen Berechnungsgraphen
  305. zunächst  einen  Ablaufgraphen.  Dieser stellt zur besseren Über-
  306. sicht eine Vereinfachung des Berechnungsgraphen dar. Seine Knoten
  307. bilden  Basisblöcke und seine Kanten kennzeichnen mögliche Abfol-
  308. gen  (analog zu t _seq im Berechnungsgraphen). Hat man nun diesen
  309. Ablaufgraphen aus dem Berechnungsgraphen konstruiert, so betreibt
  310. man nun ein wenig Datenflußanalyse. Das Ziel dieser Datenflußana-
  311. lyse ist es, zum einen überflüssige Berechnungen zu finden und zu
  312. eliminieren und zum anderen eine Konstantenweitergabe z u gewähr-
  313. leisten. Das bedeutet, man versucht, konkrete Werte von Variablen
  314. an  Programmstellen  zu bestimmen, die zur Übersetzungszeit bere-
  315. chenbar sind.
  316.  
  317. 2. Schritt:
  318. Nachdem  man  die Eliminierung überflüssiger und toter Teilstücke
  319. beendet  hat, wendet man sich nun der Schleifenoptimierung zu, da
  320. gerade  hier  die Probleme einer uneffizienten Programmausführung
  321. besonders schwer ins Gewicht fallen können. Was für Probleme kön-
  322. nen  nun  solche Schleifendurchläufe mit sich bringen? Als erstes
  323. wäre  da der Fall zu nennen, daß innerhalb einer Programmschleife
  324. Codestücke existieren, die bei jedem Schleifendurchlauf die glei-
  325. che Wirkung haben. Daher wäre es sinnvoller, derartige Codestücke
  326. vor  die  Schleife zu verschieben. Deshalb spricht man bei diesem
  327. Vorgang  auch  von  Code-Verschiebung. Einen weiteren Ansatzpunkt
  328. zur  Optimierung  bieten  die  zu einer Schleife gehörigen Induk-
  329. tionsvariablen  (Laufvariablen).  Bei  Variablen, die sich linear
  330. von  einem  Durchlauf  zum nächsten verändern, ist auf eine effi-
  331. ziente Fortschaltung zu achten. Bei FOR-Schleifen ist es zum Bei-
  332. spiel   sinnvoller,   Laufvariable   durch  entsprechende  Inkre-
  333. ment-/Dekrement-Befehle linear fortzuschalten, anstatt Additions-
  334. -/Subtraktions-Befehle  zu  verwenden. Nachdem man nun die Durch-
  335. führung  dieser  beiden Schritte beendet und einen schon ziemlich
  336. optimalen  Code  erzeugt  hat,  wendet  man  sich nun im nächsten
  337. Schritt  der sogenannten Nachoptimierung ("peephole"-Optimierung)
  338. zu.
  339.  
  340. 3. Schritt:
  341. Ziel dieser "peephole"-Optimierung ist es, im Maschinencode kurze
  342. Folgen von Befehlen durch einzelne Befehle (zum Beispiel Spezial-
  343. befehle) zu ersetzen. Bei dieser Nachoptimierung bedient man sich
  344. eines  "Fensters",  das  man über den Code schiebt und dabei nach
  345. bekannten Ersetzungsmustern sucht, bei denen ganze Befehlsgruppen
  346. zu  - im Extremfall - einem einzigen Befehl zusammengefaßt werden
  347. können. Hat man derartige Muster gefunden, so wird eine Ersetzung
  348. durchgeführt.  Beispiele für Ersetzungsmuster bei der "peephole"-
  349. Optimierung:
  350.  
  351. Ersetzungsmuster       neu eingesetztes Codestück
  352. 1. move Ri,a
  353.   move a,Ri                   move Ri,a
  354. 2. move a,b
  355.   add #c,b
  356.   move b,a                    add #c,a
  357. 3. muls #2,a                  asl #1,a
  358. 4. move #0,a                  clr a
  359.  
  360.  
  361. Mit  der  nächsten  Folge  beenden wir unser Projekt PASCOMP. Die
  362. letzte  Folge wird sich mit Erweiterungen zu unserem Compiler be-
  363. schäftigen und dem Leser an Beispielen zeigen, wie er eigene Pro-
  364. zeduren,  Funktionen  und  Typen  in die Compiler-Source einbauen
  365. kann.  Weiterhin wird ein Programm vorgestellt werden, mit dessen
  366. Hilfe  man  in  der  Lage sein wird, P-Code in Assembler-Code des
  367. M68000-Prozessors   zu  übersetzen,  um  somit  auch  "Stand-alo-
  368. ne"-Programme zu erzeugen.
  369.