2. Aufgabenblatt – Musterlösung
Transcription
2. Aufgabenblatt – Musterlösung
2. Aufgabenblatt – Musterlösung Technische Informatik II – Sommersemester 2011 Problem 2: Assembler Syntax general purpose registers Register eines 32-bit Intel-Prozessors: ⟵ 16 bits ⟶ ⟵ 8 bits ⟶ ⟵ 8 bits ⟶ EAX Accumulator Register AH AL AX EBX Base Register BH BL BX ECX Counter Register CH CL CX EDX Data Register DH DL DX ESI Source Index EDI Destination Index EBP Base Pointer ESP Stack Pointer ⟵ 32 bits ⟶ Weiterführende Informationen zu den Registern und deren Verwendung finden sich in The Art of Picking Intel Registers (http://www.swansontec.com/sregisters.html) Hallo-Welt-Programm in C “hello-world.c” #include "stdio.h" int main(int argc, int **argv) { printf("%s\n", "Hello World!"); return 0; } “gcc -m32 -S hello-world.c”, Inhalt von “hello-world.s” .file "hello-world.c" .section .rodata .LC0: .string "Hello World!" .text .globl main .type main, @function main: pushl %ebp movl %esp, %ebp andl $-16, %esp subl $16, %esp movl $.LC0, (%esp) call puts movl $0, %eax leave ret .size main, .-main .ident "GCC: (Ubuntu/Linaro 4.4.4-14ubuntu5) 4.4.5" .section .note.GNU-stack,"",@progbits Vergleichbares hello-world-Programm, handgeschrieben (AT&T-Syntax) .section .data hello: .ascii "Hello World!\n" .section .text .globl _start _start: mov $4, %eax mov $1, %ebx mov $hello, %ecx mov $13, %edx int $0x80 mov $1, %eax mov $0, %ebx int $0x80 # # # # # 4 fuer den Syscall 'write' File Descriptor Speicheradresse des Textes Laenge des Textes syscall # Exit-Code setzen # Funktion 0 (exit) auswählen # syscall Vergleichbares hello-world-Programm, handgeschrieben (Intel-Syntax) .intel_syntax noprefix .section .data hello: .ascii "Hello World!\n" .section .text .globl _start _start: mov eax, mov ebx, mov ecx, mov edx, int 0x80 4 1 offset hello 13 mov eax, 1 mov ebx, 0 int 0x80 # # # # # 4 fuer den Syscall 'write' File Descriptor Speicheradresse des Textes Laenge des Textes syscall # Exit-Code setzen # Funktion 0 (exit) auswählen # syscall Problem 3: Recherchieren Sie (Bewertete Aufgabe) Hinweis: Statt der Summe das Produkt zu berechnen, würde die Fakultät berechnen, 5000! sprengt ein 32bit Register aber um ein Vielfaches. Daher bleiben wir hier bei der Summe. Auf dem Institutsrechner andorra benötigt die handgeschriebene Assembler-Funktion grob 5000 CPU-Zyklen, die kompilierte C-Version hingegen um die 30000. Kompiliert man das Programm hingegen mit der stärksten Optimierung (gcc -O3 -m32 assembler.S framework.c), benötigt die kompilierte C-Version nur noch knapp 200 Zyklen. Woran liegt das? Betrachtet man diese simple Variante der gauss-Summe: int main() { int a = 5000; int x = 0; for (; a; a--) { x += a; } return x; } und lässt sich die generierte Assembler-Datei einmal mit und einmal ohne Optimierung anzeigen, so sieht man die folgenden beiden Programme: ohne Optimierung .file "gauss-super-simple.c" .text .globl main .type main: leal andl pushl pushl movl pushl subl movl movl jmp .L3: movl addl subl .L2: cmpl jne movl addl popl popl leal ret .size main, @function 4(%esp), %ecx $-16, %esp -4(%ecx) %ebp %esp, %ebp %ecx $16, %esp $5000, -12(%ebp) $0, -8(%ebp) .L2 -12(%ebp), %eax %eax, -8(%ebp) $1, -12(%ebp) $0, -12(%ebp) .L3 -8(%ebp), %eax $16, %esp %ecx %ebp -4(%ecx), %esp main, .-main -O3 .file "gauss-super-simple.c" .text .p2align 4,,15 .globl main .type main, @function main: leal 4(%esp), %ecx andl $-16, %esp pushl -4(%ecx) movl $12502500, %eax pushl %ebp movl %esp, %ebp pushl %ecx popl %ecx popl %ebp leal -4(%ecx), %esp ret .size main, .-main Die Unterschiede sind lila hervorgehoben. In der linken Variante erkennt man die Schleife. Da sie aber immer das gleiche ausgeben wird (12502500 nämlich), wurde in der optimierten Variante diese Schleife vom Compiler direkt durch den Wert ersetzt. TI II - Uebung 2 Felix Herter, Moritz Niklas Paul May 12, 2011 1 Aufgabe 3 Kompiliert man die framework.c erhält man für die Berechnung der Gaußschen Summe von n = 5000 die Taktzahlen: Assembler: C: 10944 60780 Der geringere Rechenaufwand der Assemblerfunktion ist leicht zu erkennen, auch wenn das auf dem Heimrechner (Intel(R) Core(TM)2 CPU 2.00GHz ) erzielte Verhältnis von ca. 1:6 auf den Uni Rechnern nicht zu reprodizieren war (Verhältnis von ca. 1:2). Die folgenden Codeauszüge zeigen die geänderten Stellen des Ursprungsprogramms, sodass sie nun die Fakultät anstelle der Gaußschen Summe ausgeben. Assembler: mov %eax, %ecx dec %ecx 1: imul %eax, %ecx dec %ecx jnz 1b C: int fak(int a){ int x=1 for(;a;a--){ x*=a; } return 0; So weit so gut, die entsprechende Ausgabe für die Berechnung der Fakultät von n = 13 liefert folgende Taktzahlen: Assembler: C: 708 960 Auch hier ist der Assembler schneller, wenn auch nicht mehr ganz so eindeutig wie bei der Summenberechnung, welche jedoch auch mit einem deutlich größeren Parameter aufgerufen wurde. Kompilieren wir nun die framework.c zusätzlich mit der Optimierungsflag -O3, so erwarten wir, dass der CCode schneller laufen wird als der Assembler-Code. Auf jeden Fall zumindest schneller, als vor dem Setzen der Flag. Die Ausgabe hingegen straft unsere Naivität mit den Werten: 1 Assembler: C: 577723137680999036 166200 Allein ein Blick auf die angebliche Anzahl der benötigten Takte, welche die Assemblerfunktion benötigt haben soll zeigt, dass es hier wohl nicht mit rechten Dingen zugehen kann: Bei den 2GHz des benutzten Prozessors ist es Recht unwahrscheinlich, dass in den Sekundenbruchteilen, die die Berechnung gedauert hat, über 5 · 1017 Takte stattgefunden haben. . . Leider war es auch auf anderen Rechnern (Netbook, Unirechner) nicht möglich zufriedenstellende Ergebnisse zu erzielen. Aufgrund erheblich Zeitmangels (ALP2 Klausur) konnten wir uns nicht so ausgiebig mit den optimierten Assemblercodes auseinandersetzen (der des framework.c betrug 134 Zeilen(!)). Zur Vereinfachung haben wir eine separate fakultaet.c geschrieben, die die Fakultät von 13 ausgibt. Optimieren und dissassemblieren führten zu: .file "fakultaet.c" .section .rodata.str1.1,"aMS",@progbits,1 .LC0: .string "13! = %i\n" .text .p2align 4,,15 .globl main .type main, @function main: pushl %ebp #Basepointer wird auf den Stack gespeichert movl %esp, %ebp #Basepointer wird auf Stackpointer gesetzt (AT&T?) andl $-16, %esp #ist %esp==-16 ? subl $16, %esp movl $1932053504, 8(%esp) #Der Compiler hat den Wert bereits ausgerechnet(!) und speichert diesen in der Adresse, auf die %esp gerade Zeigt (reserviert 8 Speichereinheiten?) movl $.LC0, 4(%esp) movl $1, (%esp) call printf chk xorl %eax, %eax #effizient %eax auf 0 gesetzt? leave ret #%eax-Wert wird zurückgegeben? .size main, .-main .ident "GCC: (Ubuntu 4.4.3-4ubuntu5) 4.4.3" .section .note.GNU-stack,"",@progbits Unsere spartanischen Vermutungen stehen als Kommentar hinter den jeweiligen Anweisungen. Allerdings ist vielleicht noch erwähnenswert, dass der Compiler selbst anscheinen bereits gesehen hat, dass das Ergebnis 1932053504 betragen wird, und dies nun einfach auf dem Stack speichert, anstatt die tatsächliche Implementierung der fakultaet.c im Assemblercode wieder zu spiegeln. 2