Arbeitsblatt #2 Einführung in die Systemprogrammierung SS 2013 10. Mai 2013 Abgabedatum: 17:00, 17.05.2013 In dieser Übung vertiefen wir die MIPS-Programmierung und beschäftigen uns mit einigen für Performanz kritischen Komponenten. 1 Taktfrequenz und Ausführungszeit [5.3, 5.5] ( 12 Punkt) Die Kennwerte CPI (Zyklen pro Instruktion) bzw. IPC (Instruktionen pro Zyklus) werden meist in Bezug auf ein gegebenes Programm berechnet. IPC ist dann die durchschnittliche Anzahl von Instruktionen, die in einem Prozessorzyklus asugeführt wird, und CPI der Kehrwert. IPC und CPI werden oft verwendet, um abzuschätzen, wie gut ein bestimmtes Stück Code optimiert ist. Auf modernen Systemen gilt dabei ein IPC-Wert knapp unter 1.0 Instruktionen pro Zyklus als Indiz für gute Optimierung. Wir verwenden diese Untersuchung nun für MIPS-Code. Die folgende MIPS-Befehlssequenz wird auf einem eingebetteten Prozessor ausgeführt, der Teil einer Gerätesteuerung ist: main: la $t0 , buffer addi $t2 , $t0 , 0 x4000 loop: lw $t1 , 0( $t0) andi $t1 , $t1 , 0x0c sw $t1 , 0( $t0) addi $t0 , $t0 , 4 bne $t0 , $t2 , loop # Messung beginnt # Messung endet Wir messen zwischen den beiden Kommentaren eine Laufzeit von 10ms, bei einer geschätzten Standardabweichung von 0.1ms (unter Annahme einer Gaußschen Normalverteilung). Wir wissen folgendes über das System: • Der Prozessor ist ein älterer MIPS-Prozessor • Der Prozessor verwendet das fünf-Phasen-Pipelining aus der Vorlesung, aber keine superskalare Ausführung • Speicherzugriff benötigt nur einen Taktzyklus • Der Prozessor ist mit 3 MHz getaktet Nicht alle Informationen sind notwendigerweise für die folgenden Aufgaben relevant. a. Berechnen Sie den IPC-Wert für das obige Programm. b. Geben Sie mindestens einen plausiblen Grund dafür an, daß die Standardabweichung nicht 0 ist. 1 2 Optimierungen [5.1, 5.4] (1 Punkt) Sie haben ein Computerspiel entwickelt, das eine virtuelle Welt simuliert. Mehrmals pro Sekunde wird dem Spieler eine zweidimensionale Projektion der dreidimensionalen Sicht auf die virtuelle Welt darstellt, daneben werden auch Geräusche abgestpielt. Ihr erster Prototyp ist nun fertig, aber das Programm läuft aus Ihrer Sicht zu langsam: gemäß Ihren Messungen kann selbst bei voller Auslastung ihres hochmodernen Rechnersystems die Sicht auf die virtuelle Welt nur 10 mal pro Sekunde aktualisiert werden. Sie schätzen, daß mindestens 30 Aktualisierungen pro Sekunde nötig sind, damit das Programm schnell genug läuft. Jede Aktualisierung wird von einem Aufruf einer Routine ‘update’ berechnet. Ein wesentlicher Teil dieser Routine ist die Kommunikation mit einem Audio-Subsystem (zuständig für die Klangverarbeitung), von dem Sie vermuten, daß es sehr ungeschickt implementiert ist. Tatsächlich ergeben Ihre Messungen, daß update volle 10ms pro Aufruf allein mit Klangverarbeitung verbringt. a. Welcher Speedup für die Routine update ist nötig, damit das Programm so schnell läuft, wie Sie es sich wünschen? b. Was ist der maximale Speedup, den Sie durch Verbesserung der Klangverarbeitung erreichen können? c. Beschreiben Sie Ihre nächsten Schritte, um mit möglichst wenig Aufwand Fortschritt in Richtung des gewünschten Speedups zu machen. 3 Pipeline-Ausführung [6.1, 6.2] (1 Punkt) Betrachten Sie das folgende Assemblerprogramm: nop nop nop nop nop # # # # # Der ‘nop ’- Befehl hat keine Wirkung [00 F0] [00 F4] [00 F8] [00 FC] start : ori lui ori lw add sw nop nop nop nop $t0 , $t1 , $t1 , $t2 , $t2 , $t2 , $zero , 2 0 x00a0 $ti , 0 x1a80 0( $t1) $t2 , $t0 0( $t1) # [0118] # [011C] # [0120] # [0124] # # # # # # [0100] [0104] [0108] [010C] [0110] [0114] Wir führen dieses Programm auf dem 5-Phasen-Pipelineprozessor aus der Vorlesung aus. a. Beschreiben Sie ab der Sprungmarke start, was die Inhalte der verschiedenen Phasen nach jedem Zyklus sind. Falls Sie die Tabelle auf der nächsten Seite dazu verwenden, überspringen Sie dabei die Zeilen, die mit Durchreichen markiert sind. b. Falls Registerwerte von den Zwischenregistern durchgereicht wreden müssen, markieren Sie dies durch Pfeile in der Tabelle in den mit Durchreichen markierten Zeilen. Verbinden Sie dabei die Trennlinien, die an der Position der durchreichenden Register in der Tabelle stehen. Geben Sie den Namen des betroffenen Registers an. Falls Sie die Übung nicht auf Papier einreichen, beschreiben Sie das Durchreichen, indem Sie die Zwischenregister angeben, zwischen denen durchgereicht wird, z.B. ‘DS/RS $t0 nach ID/AUS durchgereicht’. 2 ID AUS DS RS Reg PSp 00FC 00F8 ALU ALU IL DSp Reg 00F4 00F0 00EC Durchreichen: Durchreichen: Durchreichen: Durchreichen: Durchreichen: Durchreichen: Durchreichen: Durchreichen: Durchreichen: Durchreichen: Durchreichen: 4 Taktfrequenz und Ausführungszeit: Optimierungen [6.2] ( 12 Punkt) Kehren Sie zurück zur ersten Aufgabe dieses Übungsblattes. Ändern Sie die Schleife dort ab, so daß sie weniger Taktzyklen zur Ausführung benötigt. 5 Verzögerte Verzweigung [6.4] (1 Punkt) Gegeben sei ein MIPS-Prozessor unbekannter Version. Schreiben Sie ein Programm, um herauszufinden, ob dieser Prozessor verzögerte Verzweigung verwendet oder nicht. Diese Eigenschaft soll alleine an der Ausgabe des Programmes erkennbar sein. Führen Sie das Program unter WebSPIM aus. a. Verwendet dieser Prozessor verzögerte Verzweigung? b. Erklären Sie, wie und warum die Ausgabe Ihres Programmes dies belegt. 6 Sortierter Binärbaum [4] (6 Punkte) Ein sortierter Binärbaum mit 32 Bit-Zahlen ist ein gerichteter azyklischer Graph G = hV, E1 , E2 , W, i der aus Knoten V besteht, mit einem designierten Wurzelknoten ∈ V . Die partiellen Funktionen E1 , E2 : V → V⊥ bilden Knoten auf ihre Kindknoten ab (sofern diese existieren), während die Wertfunktion W : V → Int32 jeden Knoten auf eine vorzeichenbehaftete 32-Bit-Zahl in Zweierkomplementdarstellung, den Wert des Knotens, abbildet. 3 Dabei gilt die Invariante I1 , daß I1 : E1 (v) = v 0 ⇒ W (v 0 ) < W (v) also Kinder über E1 nur auf Knoten mit geringerem Wert, und die Invariante I2 , daß I2 : E2 (v) = v 0 ⇒ W (v 0 ) > W (v) also Kinder über E2 nur auf Knoten mit höherem Wert abgebildet werden. Zum Beispiel: Wurzel 5 -3 7 6 Der Wurzelknoten im obigen Beispiel hat den Wert 5 und zwei Kindknoten. Der linke Kindknoten (über E1 ) hat den kleineren Wert −3 und selbst keine Kindknoten. Der rechte Kindknoten (über E2 ) des Wurzelknotens wiederum hat den Wert 7 und einen weiteren Kindknoten (über E1 ), mit dem Wert 6. In dieser Aufgabe implementieren wir Binärbäume in MIPS-Assembler, allerdings ohne den üblichen Balancierungsschritt. Es gibt viele Möglichkeiten, Mengen wie V und (partielle) Funktionen wie E1 , E2 , W im Speicher abzubilden. In der Praxis ist es oft am effizientesten, Funktionen zu sammeln, die alle die gleiche Definitionsmenge (V , in diesem Fall) haben, und deren Abbildungen für jedes Element der Definitionsmenge zusammenzufassen. In höheren Sprachen geschieht dies in Datenstrukturen; in Assembler simulieren wir diese Datenstrukturen durch programmspezifische Konventionen. In unserem Fall können wir also für jeden Knoten v die Abbildungen E1 (v), E2 (v), W (v) zusammen speichern; die Repräsentierung des Knotens v im Speicher entspricht also einem Tripel dieser drei Abbildungen. Wir repräsentieren dabei Werte W (v) als normale Zweierkomplementzahlen, aber es ist weniger offensichtlich, wie wir Kindknoten E1 (v), E2 (v) repräsentieren sollten, und es existieren verschiedene Lösungen für dieses Repräsentierungsproblem. Die häufigste Lösung ist die, daß Kindknoten v 0 durch ihre Speicheradressen repräsentiert werden. Da die Repräsentierung der Knoten Platz im Speicher einnimmt, können wir die Adressen dieses Speichers als Bezeichner für den betreffenden Knoten verwenden. Wenn also ein Kindknoten v 0 ab Adresse 0x2000 im Speicher liegt (und z.B. den Wert W (v 0 ) von Adresse 0x2000 bis einschließlich 0x2003 speichert), dann kann der Elternknoten v sich den Kindknoten v 0 merken, indem er sich eine der Adressen von v 0 merkt; per Konvention ist dies meist die kleinste Adresse, also 0x2000 in unserem Beispiel. Die folgenden Teilaufgaben können Sie separat oder in Kommentaren innerhalb Ihres Programmes beantworten. a. Folgen Sie den obigen Ausführungen und entwerfen Sie eine Speicherbelegung für Knoten des Graphs, so daß jeder Knoten eine konstante Menge an Speicher einnimmt. Wieviele Bytes nimmt jeder Knoten ein? b. Beschreiben Sie für jedes Byte Ihrer Speicherbelegung, welche Bedeutung dieses Byte bzgl. des Graphen G hat. (Wenn mehrere Bytes in Folge eine verwandte Bedeutung haben, können sie diese zusammen beschreiben und müssen nicht jedes Byte einzeln ausführen.) Wie behandeln sie Situationen, in denen dem Knoten ein oder mehrere Kindknoten fehlen? c. Schreiben Sie den Rumpf eines MIPS-Programmes, das mit Knoten Ihres Baumes arbeitet. Allozieren Sie dazu genug Platz im statischen Speicher (.data), um mindestens 100 Knoten aufzunehmen. 4 Schreiben Sie eine Subroutine alloc node, die die Adresse des nächsten freien Knotens in Ihrem statischen Speicher zurückliefert. Jeder Aufruf dieser Routine sollte also eine ‘frische’ Adresse als Ergebnis zurückliefern und sicherstellen, daß an der gegebenen Adresse genug Platz ist, um einen Baumknoten zu speichern. Was passiert, wenn Ihr alloc node häufiger aufgerufen wird, als Platz verfügbar ist? d. Entscheiden Sie sich für eine Repräsentierung des Wurzelknotens und beschreiben Sie diese. e. Schreiben Sie die Hauptroutine der Baumbehandlung, eine Subroutine find node. Ihre Routine sollte einen Parameter w nehmen und vom Wurzelknoten aus den Knoten v finden, der diesen Wert w hat. Beachten Sie dabei für jeden Knoten v 0 , den Sie besuchen, folgendes: • Wenn W (v 0 ) = w, dann haben Sie den korrekten Knoten gefunden. • Wenn W (v 0 ) > w, dann müssen Sie im Kindknoten E1 (v 0 ) weitersuchen, sofern dieser existiert. • Wenn W (v 0 ) < w, dann müssen Sie im Kindknoten E2 (v 0 ) weithersuchen, sofern dieser existiert. Falls kein Knoten v mit W (v) = w existiert, soll die Routine stattdessen den letzten besuchten Knoten v 0 zurückliefern. f. Schreiben Sie eine Routine has value, die einen Parameter (einen Wert) nimmt und überprüft, ob dieser Wert in dem Baum gespeichert ist. Die Routine soll 1 zurückliefern falls der Wert im Baum abgelegt ist, ansonsten 0. Verwenden Sie die vorher implementierten Routinen nach Bedarf, um Arbeit zu sparen. g. Schreiben Sie eine Routine add value, die einen Parameter (einen Wert) nimmt und überprüft, ob dieser Wert in dem Baum gespeichert ist. Falls nicht, fügt die Routine den Wert in einem neuen Knoten in den Baum ein. Stellen Sie sicher, daß die Routine die Invarianten I1 und I2 gewährleistet! Verwenden Sie die vorher implementierten Routinen nach Bedarf, um Arbeit zu sparen. h. Schreiben Sie ein Hauptprogramm (main), das Zahlen vom Benutzer einliest und in Ihren Binärbaum schreibt, bis der Benutzer die Zahl 0 eingibt (die nicht geschrieben wird). Für alle danach eingegebenen Zahlen schauen Sie nach, ob die Zahl im Binärbaum gespeichert wurde oder nicht, und geben eine entsprechende Meldung aus. 5