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