Algorithmen und Datenstrukturen 1 Kapitel 4.1 Technische Fakultät robert@techfak.uni-bielefeld.de Vorlesung, U. Bielefeld, Winter 2005/2006
Kapitel 4: Maschinenmodelle [Dieses Kapitel hält sich eng an das empfohlene Buch von Saake/Sattler] Das Rechnen mit Formeln und Gleichungen ist uns aus der Mathematik vertraut. Mit unseren Haskell-Programmen können wir auch selbst noch rechnen. Eine Rechenmaschine, die direkt mit Formeln und Gleichungen rechnen kann, gibt es nicht. Wirkliche Rechner sind viel einfacher aufgebaut, und brauchen viele Operationen, um z.b. eine Gleichung einmal anzuwenden. Dies ist die in Kapitel 1 erwähnte semantische Lücke, die durch Programmiersprachen und ihre Übersetzer geschlossen wird. Wir begeben uns nun auf die Maschinenseite der semantischen Lücke.
4.1 Registermaschinen Eine Registermaschine ist eine formal definierte Rechenmaschine relativ ähnlich zu wirklicher Hardware (insbesondere der frühen Jahre)
Komponenten der Registermaschine Programm speicher b b 1 2 b 3 b n Zentrale Recheneinheit B + * Recheneinheit Steuer einheit C 0 = Arbeits speicher C 1 C 2 C 3 C 4 b 1... n B C C + b : : 0 : 1, C2,... :,, = : * Programm ( n Befehle) Befehlszähler Akkumulator (Arbeitsregister) Arbeitsspeicher (Register) Rechenwerte Unsere Registermaschine kennt nur zwei Datentypen natürliche Zahlen und Befehle.
Befehlszyklus Die Registermaschine iteriert einen Befehlszyklus: Sei i der Inhalt von B. Lade b i aus dem Programmspeicher in die Steuereinheit. Erhöhe B auf i + 1. Decodiere b i und führe ihn aus durch Laden von Operanden. Durchführung von Rechenoperationen. Abspeichern des Ergebnisses. Die Ausführung hängt natürlich im Einzelnen von dem konkreten Befehl ab.
Programm Das Programm ist eine Folge von Befehlen b 1, b 2,..., b n Diese ist unveränderbar (im Unterschied zu realen Rechnern, wo Programme in den Programmspeicher geladen werden).
Konfiguration Für den Akkumulator C 0 schreiben wir auch C. Enthält B die Zahl b, und C i die Zahl c i, 0 i, so heisst (b, c 0, c 1,... ) aktuelle Konfiguration der Registermaschine. Die Ausführung eines Befehls bewirkt einen Konfigurations-Übergang (b, c 0, c 1,... ) (b, c 0, c 1,... ) Befehle werden als Konfigurations-Übergänge definiert.
Laden und Speichern LOAD i, i N + b = b + 1 c 0 = c i c j = c j für j 0 CLOAD i, i N b = b + 1 c 0 = i c j = c j für j 0 STORE i, i N + b = b + 1 c i = c 0 c j = c j für j i
Arithmetische Befehle ADD i, i N + b = b + 1 c 0 = c 0 + c i c j = c j für j 0 CADD i, i N + b = b + 1 c 0 = c 0 + i c j = c j für j 0 SUB i, i N + b = b + 1 c 0 = j c0 c i für c 0 c i 0 sonst CSUB i, i N + b = b + 1 c 0 = j c0 i für c 0 i 0 sonst c j = c j für j 0 c j = c j für j 0 MULT i, i N + b = b + 1 c 0 = c 0 c i c j = c j für j 0 CMULT i i N + b = b + 1 c 0 = c 0 i c j = c j für j 0 DIV i, i N + b = b + 1 c 0 = c 0/c i c j = c j für j 0 CDIV i, i N + b = b + 1 c 0 = c 0/i c j = c j für j 0
Arithmetik auf natürliche Zahlen beschränkt: statt negativer Differenz ist das Ergebnis 0. Division erfolgt ganzzahlig
Sprungbefehle GOTO i, i N + b = i c j = c j für j 0 j i falls IF c 0 = 0 GOTO i, i N + b c0 = 0 = b + 1 sonst c j = c j für j 0 END b = b c j = c j für j 0
Maschinen und Programmabläufe Registermaschinen unterscheiden sich durch ihr Programm. Verschiedene Programmabläufe unterscheiden sich durch die Start-Konfiguration (alles weitere ist ja festgelegt). Start Konfiguration x i sind die Eingabe-Parameter (1, 0, x 1,..., x n, 0, 0,... )
Berechnete Funktion M berechnet Funktion f M : N n N m, falls für alle x 1,..., x n N gilt (1, 0, x 1,..., x n, 0, 0,... ) (b e, c, y 1,..., y m,... ) und (y 1,..., y m ) = f (x 1,..., x n ) und b e ist ein END-Befehl.
Beispiel M + 1 LOAD 1 2 ADD 2 3 STORE 3 4 END (1, 0, a, b) (4, (a + b), a, b, a + b)
Beispiel M fib 1 LOAD 1 2 ADD 2 3 STORE 2 4 SUB 1 5 STORE 1 6 END (1, 0, a, b) (6, b, b, a + b) M fib berechnet einen Schritt der Fibonacci-Iteration.
Beispiel M 1 1 LOAD 1 2 DIV 2 3 MULT 2 4 STORE 3 5 LOAD 1 6 SUB 3 7 STORE 3 8 END
Rechnung von M 1 mit r = n (n div m) m Berechnete Funktion: (1, 0, n, m, 0)...... (8, r, n, m, r) f (x 1, x 2 ) = (x 1, x 2, x 1 mod x 2 )
Semantik einfacher Programme Beispiel M id Beispiel M undef 1 END 1 GOTO 1 2 END f (x 1,..., x n ) = (x 1,..., x n ) f (x 1,..., x n ) = undefiniert
Beispiel M 3 Beispiel M 4 1 LOAD 1 1 LOAD 1 2 IF c 0 = 0 GOTO 4 2 IF c 0 = 0 GOTO 1 3 GOTO 2 3 END 4 END f (x 1 ) = 0 falls x 1 = 0 f (x 1 ) = x 1 falls x 1 > 0 = undefiniert, sonst = undefiniert, sonst
Beobachtungen zu Registermaschinen Die Anzahl der benutzten Register liegt statisch fest. Alle Adressen stehen explizit im Programm. Die Anzahl der Befehl bei Ausführung ist statisch nicht bestimmbar und hängt insbesondere von den (bedingten) Sprüngen ab. Programme ohne Schleifen kann man symbolisch ausführen und so die berechnete Funktion bestimmen. Bei Programmen mit Schleifen führt die symbolische Ausführung nicht zu einer Bestimmung der berechneten Funktion.
Invarianten Um die berechnete Funktion von Programmen mit Schleifen zu finden, benutzt man Invarianten. Diese sind Aussagen über Konfigurationen, die an gewissen Programmstellen immer erfüllt sind: φ bb (c 0, c 1,... ) gilt am Übergang von Befehl b zu b. φ start (c 0, c 1... ) gilt am Anfang, φ end (c 0, c 1... ) am Ende.
Invarianten zu M 3 1 2 3 φ start = (c 1 = x) φ 12 = (c 0 = c 1 = x) φ 23 = (c 0 = c 1 > 0) φ 32 = (c 0 = c 1 > 0) φ 24 = (c 0 = c 1 = 0) φ END = (c 1 = 0) 4 Bei der ersten Ausführung von Befehl 2 gilt φ 12, bei eventuellen weiteren die stärkere Invariante φ 23 = φ 32. Wird also Punkt 3 einmal erreicht, liegt Endlosschleife vor. Punkt 4 wird erreicht genau dann, wenn x = 0.
Invarianten zu M 4 1 2 3 φ start = (c 1 = x) φ 12 = (c 0 = c 1 = x) φ 23 = (c 0 = c 1 = x > 0) φ 21 = (c 0 = c n = 0 = x) φ END = (c 1 = x > 0)
Registermaschine M 2 1 CLOAD 1 2 STORE 3 3 LOAD 2 4 IF c 0 = 0 GOTO 12 5 LOAD 3 6 MULT 1 7 STORE 3 8 LOAD 2 9 CSUB 1 10 STORE 2 11 GOTO 4 12 END
Programmflussdiagramm für M 2 1 2 3 4 5 6 7 8 12 11 10 9 φ start = (c 1 = x, c 2 = y) Wir suchen φ end, indem wir φ start durch das Diagramm propagieren. Das geht überall einfach, außer am Schleifenkopf 4.
φ start = (c 1 = x, c 2 = y) φ 12 = (c 0 = 1, c 1 = x, c 2 = y) φ 23 = (c 0 = 1, c 1 = x, c 2 = y, c 3 = 1) φ 34 = (c 0 = y, c 1 = x, c 2 = y, c 3 = x) Hier entsteht nun das Problem, dass wir φ 45 nicht allein aus φ 34 und b 4 bestimmen können, da es auch von φ 11 4 abhängt, das wir noch nicht kennen.
Idee: Wir fügen eine neue, noch unbekannte Invariante φ 4 für den Schleifenkopf ein. Für sie muss gelten φ 34 oder φ 11 4 φ 4. Dabei hängt φ 11 4 natürlich von φ 4 ab. Mit dem noch unbekannten φ 4 rechnen wir weiter.
Sei φ 4 = (c 0 = a, c 1 = x, c 2 = b, c 3 = d) mit Unbekannten a, b, d. φ 45 = (c 0 = a > 0, c 1 = x, c 2 = b, c 3 = d) φ 56 = (c 0 = d, a > 0, c 1 = x, c 2 = b, c 3 = d) φ 67 = (c 0 = d x, a > 0, c 1 = x, c 2 = b, c 3 = d) φ 78 = (c 0 = d x, a > 0, c 1 = x, c 2 = b, c 3 = d x)
φ 89 = (c 0 = b, a > 0, c 1 = x, c 2 = b, c 3 = d x) φ 9 10 = (c 0 = b 1, a > 0, c 1 = x, c 2 = b, c 3 = d x) φ 10 11 = (c 0 = b 1, a > 0, c 1 = x, c 2 = b 1, c 3 = d x) φ 11 12 = Q 10 11
Jetzt müssen wir φ 4 herausfinden. Es muss gelten φ 34 φ 4 und φ 11 4 φ 4 φ 34 : (c 0 = y, c 1 = x, c 2 = y, c 3 = 1) φ 11 4 : (c 0 = b 1, c 1 = x, c 2 = b 1, c 3 = d x, a > 0) φ 4 : (c 0 = a, c 1 = x, c 2 = b, c 3 = d)
Den Effekt eines Schleifendurchlaufs auf φ 4 hält φ 11 4 fest. Wir beobachten c 1 ändert sich nicht, c 0, c 2 und c 3 ändern sich. Für φ 11 4 wie für φ 4 gilt invariant: c 3 = x (y c 2). Wir setzen als neue Invariante Es gilt φ 34 impliziert ˆφ 4. ˆφ 4 = c 3 = x (y c 2), c 0 = c 2 Es gilt nach Durchschieben von ˆφ 4 durch die Schleife ) ˆφ 11 4 = (c 3 = x (y (c 2+1)) x, c 0 = c 2 = ˆφ 4
Damit finden wir nun die Endkonfiguration φ 412 = (φ 4, c 0 = 0) = (c 3 = x (y c2), c 0 = c 2 ) = (c 3 = x y ) φ END = (c 3 = x y ) Damit ist bewiesen, dass die Maschinenfunktion von M 4 lautet f M4 (x, y) = x y Allerdings gilt dies nur, wenn Punkt 12 erreicht wird. Im Beispiel ist die Terminierung einfach zu zeigen, im allgemeinen aber schwer.
Partielle versus Totale Korrektheit Ein Maschinenprogramm M heisst partiell korrekt für eine Funktion f : N n N m, falls f M = f, sofern f M terminiert. M heisst total korrekt für f, falls gilt: Wenn f (x 1,..., x n ) definiert ist, so terminiert f M auf Eingabe x 1,..., x n mit dem Ergebnis f (x 1,..., x n ).
Nachweis der Korrektheit Für Registermaschinenprogramme wie für alle imperativen Programme weist man partielle Korrektheit durch die Bestimmung der Invarianten nach. Anstelle der Terminierung zeigt man lieber eine stärkere Aussage: Man gibt an, nach wievielen Schritten eine Berechnung endet Kapitel Effizienzanalyse.