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