Projekt - Sprawozdanie

Transkrypt

Projekt - Sprawozdanie
Języki Asemblerowe
Rok
akademicki
2008/2009
Termin
poniedziałek
9:30-11:00
Rodzaj
studiów
Kierunek
Prowadzący
Grupa
Sekcja
Dzienne
Inf
AO
1
1
Projekt - Sprawozdanie
Temat ćwiczenia:
Raytracer 4-wymiarowych fraktali Julia
Tomasz bla Fortuna
1 Temat
Tematem projektu było napisanie programu generującego statyczne obrazy rzutu czterowymiarowych fraktali Julia na przestrzeń trójwymiarową.
1.1 Założenia
• Wykorzystanie jednostki SIMD do obliczeń zmiennoprzecinkowych.
• Napisanie programu na architekturę x86.
• Podział rozwiązania problemu na dowolną liczbę wątków.
• Napisanie programu w sposób umożliwiający jego następne przeniesienie na system
Windows oraz platformę x86 64.
2 Analiza zadania
Zbiór Julii tworzą punkty P (x, y, z, w) ∈ Z takie, że ciąg:
z0 = x + yi + zj + wk
zn+1 = zn2 + c
(1)
nie dąży do nieskończoności.
Parametr fraktala c, zdefiniowany jest następująco:
c = c1 + c2 i + c3 j + c4 k
2.1 Metody renderowania
Fraktale tego typu można renderować na dwa sposoby:
1. Poprzez obliczanie pola skalarnego fraktala (obliczając z zadaną ziarnistością granicę
ciągu ?? dla fragmentu przestrzeni, w której powinnien znajdować się fraktal).
Następnie skonwertować na siatkę trójkątów za pomocą algorytmu maszerujących
sześcianów (marching cubes) oraz wyświetlić za pomocą np. OpenGL.
2. Za pomocą algorytmu śledzenia promieni.
Wybrałem drugi z algorytmów ze względu na jego liczne zalety. Jest szybszy, generuje
obrazy bez widocznej ziarnistości związanej z doborem rozdzielczości próbkowania pola
skalarnego oraz można go w całości zaimplementować samemu bez uciekania się do bibliotek takich jak OpenGL. Daje się go również zrównoleglać o wiele wygodniej niż algorytm maszerujących sześcianów.
2
2.2 Algorytm
1. Wygenerowanie kwaternionów opisujących kamerę.
2. Dla każdego piksela ekranu (x, y) szukamy przecięcia z fraktalem:
a) Stwórz promień (para kp , kd — kwaternion początku + kierunku) dla zadanego
punktu.
b) Dopóki kwaternion początku znajduje się w pewnym otoczeniu fraktala oraz
jego odległość od fraktala jest większa od powtarzaj:
i. Oszacuj granicę ciągu ??.
ii. Określ odległość kwaternionu początku od fraktala i przesuń ten kwaternion w kierunku promienia: kP = kp + kd ∗ d
c) Jeśli znaleziono kolizję oblicz wektor normalny i zapisz kolor wynikający z
modelu Phong.
d) Jeśli nie ma kolizji przypisz pikselowi kolor czarny.
3. Wyświetl wygenerowany obraz
3 Specyfikacja zewnętrzna
Program jest uruchamiany z linii poleceń, w której można zdefiniować rozdzielczość
generowanego ekranu oraz liczbę wątków na jaką ma być podzielona praca. Następnie
program uruchamia wątki, dzieli zadany obraz na fragmenty i uruchamia rendering.
Przykład uruchomienia:
% ./Main.x86_asm_sse 640 480 8
Rendering 640x480 picture with 8 threads
Using hand-crafted SSE2 code
Rendering using: CPU only
Initializing display with 640x480 size
thread 0 = from (0 0) to (80 480) [size: 80x480]
thread 1 = from (80 0) to (160 480) [size: 80x480]
thread 2 = from (160 0) to (240 480) [size: 80x480]
thread 3 = from (240 0) to (320 480) [size: 80x480]
thread 4 = from (320 0) to (400 480) [size: 80x480]
thread 5 = from (400 0) to (480 480) [size: 80x480]
thread 6 = from (480 0) to (560 480) [size: 80x480]
thread 7 = from (560 0) to (640 480) [size: 80x480]
Realtime = 0.26905
Parametr c fraktala, parametry kamery (położenie, kierunek, FOV) oraz, w przypadku generowania ciągu klatek celem wyrenderowania animacji, ilość klatek, iterowany
parametr, są na razie na sztywno zapisane w funkcji main. Planuję wczytywać je wszystkie z wiersza poleceń.
3
4 Specyfikacja wewnętrzna
Specyfikacja wewnętrzna (opis funkcji oraz struktur) została wygenerowana za pomocą
Doxygen i jest dostępna na płycie z projektem.
Program jest napisany w języku C oraz Assembler. W trakcie kompilacji są generowane
dwa pliki .o — jeden z części HLL, drugi z kodu assembler, które są ze sobą linkowane
tworząc gotowy wynikowy program, w którym język C może bezpośrednio wołać funkcje
assemblera.
Ponadto w trakcie kompilacji można zdefiniować za pomocą stałych parę parametrów
programu.
• USE ASM, włącza implementacje algorytmu w języku asm.
• USE CUDA, włącza implementacje realizowaną na procesorze graficznym
• USE HALFCUDA, ponadto dzieli obraz pomiędzy GPU i CPU w celu przyśpieszenia
obliczeń.
Na razie ustawienie flagi USE ASM wymaga (cross)kompilacji na platformę x86, natomiast ze względu na ograniczenia użytego SDK CUDA, włączenie USE CUDA wymaga
użycia platformy na którą był kompilowany SDK. W moim przypadku jest to x86 64.
Do wyświetlania grafiki program używa biblioteki SDL.
Za celowe uważam umieszczenie głównego fragmentu kodu obliczającego wartość ciągu.
W wielu miejscach gdzie to było możliwe przedzielałem instrukcje, których argumenty
były zależne od wyników poprzedniej instrukcji innymi, w celu ułatwienia procesorowi
równoległego wykonywania zadań. Ze względu na to, że utrudnia to odczytywanie kodu
starałem się obficie te zmiany komentować
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
;;−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
; ; Functions s t e p s the J u l i a equation
; ; d {n+1} = z n ∗ d n ∗ 2 . 0
(1)
; ; z {n+1} = z n ˆ2 + c
(2)
; ; And t h e n e s t i m a t e s d i s t a n c e from J u l i a
; ; and r e t u r n s r e s u l t i n s t 0
julia limit :
push ebp
mov ebp , e s p
prefetch [ julia d0 ]
; ; Data f e t c h
; ; xmm6 − z bound a c r o s s l o o p e x e c u t i o n
; ; xmm7 − d
; 00
01
10
11
; z.x , z.y , z.z , z.w
; d . x , d.y , d . z , d.w
. prologue :
; ; Read z0 from s t a c k
4
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
mov ecx , [8+ ebp ]
movupd xmm6, oword [ ecx ]
; ; I n i t i a l i z e d w i t h d0
movapd xmm7, oword [ j u l i a d 0 ]
movapd xmm3, oword [ j u l i a m u l 1 ]
movapd xmm4, oword [ j u l i a m u l 2 ]
movapd xmm5, oword [ j u l i a m u l 3 ]
; ; Limit loop
mov ecx , [ j u l i a i t e r ]
prefetch [ j u l i a i n f ]
. loop :
dec ecx
push ecx
; ; C a l c u l a t e d {n+1}
. equat1 :
;
(1)
(2)
;
x = a.x ∗ b.x − a.y ∗ b.y −
;
y = a.x ∗ b.y + a.y ∗ b.x +
;
z = a . x ∗ b . z − a . y ∗ b.w +
;
w = a . x ∗ b.w + a . y ∗ b . z −
;
o r d e r i n r e g i s t e r : 11 10 01 00
a.z
a.z
a.z
a.z
(3)
∗ b.z
∗ b.w
∗ b.x
∗ b.y
−
−
+
+
a.w
a.w
a.w
a.w
(4)
∗ b.w ;
∗ b.z ;
∗ b.y ;
∗ b.x ;
00
01
10
11
; ; Prepare s e t o f A.x and m u l t i p l y them by b . x , b . y , b . z , b.w
pshufd xmm0, xmm6, 00000000 b
; (1) set of a.x
pshufd xmm1, xmm6, 01010101 b
; (2) set of a.y
mulps xmm0, xmm7
; (1)
x o r p s xmm1, xmm3
; ( 2 ) change s i g n
pshufd xmm2, xmm7, 10110001 b
mulps xmm2, xmm1
pshufd xmm1, xmm6, 10101010 b
addps xmm0, xmm2
x o r p s xmm1, xmm4
;
;
;
;
;
( 2 ) ” swap i n p a i r s ”
(2)
(3) set of a.z
(1+2)
( 3 ) change s i g n
pshufd xmm2, xmm7, 01001110 b
mulps xmm2, xmm1
pshufd xmm1, xmm6, 11111111 b
addps xmm0, xmm2
x o r p s xmm1, xmm5
;
;
;
;
;
( 3 ) ” swap i n qwords ”
(3)
( 4 ) s e t o f a.w
(1+2+3)
( 4 ) change s i g n
pshufd xmm7, xmm7, 00011011 b
; ( 4 ) ” swap i n qwords and i n p a i r s ”
5
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
mulps xmm7, xmm1
addps xmm7, xmm0
; (4)
; (1+2+3+4)
addps xmm7, xmm7
; ∗ 2
prefetch [ julia zmul ]
prefetch [ j u l i a c ]
. equat2 :
;
;
;
;
x
y
z
w
=
=
=
=
1
2
2
2
∗
∗
∗
∗
x
x
x
x
∗
∗
∗
∗
x
y
z
w
−
+
+
+
y ∗ y − z ∗ z − w∗w + c
c
c
c
(1)
(2)
(3)
(4)
pshufd xmm0, xmm6, 00000000 b
mulps xmm0, [ j u l i a z m u l ]
mulps xmm0, xmm6
; ; Create a s e t of z . x
; ; M u l t i p l y by 1 , 2 , 2 , 2
; ; M u l t i p l y by z . x , z . y , z . z , z . w
mulps xmm6, xmm6
addps xmm0, [ j u l i a c ]
; ; Square x , y , z and w ( x d i s m i s s e d )
; ; Add c c o n s t a n t
p s r l d q xmm6, 4
s u b s s xmm0, xmm6
; ; e x t r a c t z . y ˆ2
; ; sub z . y ˆ2
p s r l d q xmm6, 4
s u b s s xmm0, xmm6
; ; e x t r a c t z . z ˆ2
; ; sub z . z ˆ2
; ; e x t r a c t z . z ˆ2
p s r l d q xmm6, 4
; ; sub z . w ˆ2
s u b s s xmm0, xmm6
; ; p l a c e i t b a c k i n xmm6
movaps xmm6, xmm0
; ; C a l c u l a t e z l e n and c h e c k i f we can b r e a k f r e e
; ; Square
mulps xmm0, xmm0
movhlps xmm1, xmm0
; ; Sum two p a i r s
addps xmm0, xmm1
pshufd xmm1, xmm0, 01010101 b
; ; Sum r e s t
a d d s s xmm0, xmm1
; ; i f smaller continue
c o m i s s xmm0, [ j u l i a i n f ]
jb . lo op e pi lo gu e
6
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
pop ecx
jmp . e p i l o g u e
. loop epilogue :
pop ecx
o r ecx , ecx
jnz . loop
. epilogue :
; ; Calculate zlen
s q r t s s xmm0, xmm0
; ; C a l c u l a t e s d l e n g i v e n ’ d ’ i n xmm7 r e g i s t e r
. julia get dlen :
mulps xmm7, xmm7
movhlps xmm5, xmm7
addps xmm7, xmm5
pshufd xmm5, xmm7, 01010101 b
a d d s s xmm7, xmm5
s q r t s s xmm7, xmm7
. get distance :
; ; xmm0 − z l e n ; xmm7 − d l e n
; d i s t = l o g ( z l e n ) ∗ z l e n / (2∗ d l e n ) ;
; ; Logarithm :
; a ( x ) = ( x −1) / x
; log ( x ) = a( x ) ∗ ( 1 + 0.5∗ a( x ) )
; ; Simplified :
; d i s t = ( z l e n −1)/ z l e n ∗ z l e n ∗ (1+0.5 ∗ a ) / (2∗ d l e n ) ;
;= ( z l e n −1) ∗ (1 + 0 . 5 ∗ ( z l e n −1)/ z l e n ) / (2∗ d l e n )
;;
;;
;;
;;
;;
;;
;;
(1)
(2)
(3)
(4)
(5)
(6)
(7)
Constants
xmm1 = z l e n − 1
xmm3 = ( z l e n −1) / z l e n
xmm3 /= 2
xmm3 += 1
xmm1 = ( z l e n −1) ∗ ( . . . )
xmm1 /= (2∗ d l e n )
movss xmm2, [ one ]
; (1)
movss xmm1, xmm0
s u b s s xmm1, [ one ]
; (2)
; (2)
movss xmm4, [ two ]
; (1)
7
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
movss
divss
divss
divss
xmm3,
xmm1,
xmm3,
xmm1,
xmm1
xmm7
xmm0
xmm4
;
;
;
;
(3)
(7)
(3)
(7)
d i v s s xmm3, xmm4
; (4)
a d d s s xmm3, [ one ]
; (5)
mulss xmm1, xmm3
movss [ esp −4] , xmm1
; (6)
;;
; ; Leave r e t u r n v a l u e as c a l l i n g c o n v e n t i o n wants i n s t 0
;;
f l d dword [ esp −4]
pop ebp
ret
5 Problemy
Największym problemem, który trzeba było rozwiązać było wymyślenie dobrej metody
wykonywania mnożenia kwaternionów za pomocą SSE. Istnieją dwa podstawowe podejścia.

sx = zx dx − zy dy − zz dz − zw dw



sy = zx dy + zy dx + zz dw − zw dz
s = zx dz − zy dw + zz dx + zw dy


 z
sw = zx dw + zy dz − zz dy + zw dx
Pierwsze podejście zakłada obliczenie jednego mnożenia z ∗ d na raz, i zrównoleglanie
mnożeń występujących w równaniu. Jego wadą jest to, że wymaga częstych zmian
kolejności wartości zmienno przecinkowych znajdujących się w rejestrach xmm.
Drugie podejście zakłada zrównoleglanie jednoczesnego obliczania 4 równań.
Wybrałem pierwszą metodę, choć ze względu na optymalność obliczeń druga prawdopodobnie wyszła by znacznie lepiej. Druga metoda wymaga każdorazowego konwertowania danych przed wrzuceniem do SSE albo przechowywania ich przez cały czas nie
jako kwaternionu, tylko jako współrzędnych x, 4 różnych kwaternionów. Uniemożliwia
również optymalizacje polegające na przerwaniu obliczeń ciągu jeśli jego wartość dąży
do nieskończoności - ponieważ inny obliczany w tym samym momencie ciąg mógłby mieć
granicę właściwą.
6 Wnioski
Przetestowałem wszystkie skompilowane wersje tego programu pod kątem wydajności:
8
Obie zastosowane metody optymalizacji - podział na wątki, oraz przepisanie często
wykonywanego kodu do assemblera i zoptymalizowanie za pomocą SSE - przyniosły
widoczne rezultaty. Kod optymalizowany ręcznie wykonuje się znacznie szybciej od
kodu generowanego przez kompilator - zarówno na x87 jak i na SSE2.
Nie można natomiast zauważyć znaczącej różnicy pomiędzy kodem generowanym przez
kompilator na architekturę 32 bitową i 64 bitową — jeżeli w obu przypadkach korzysta on
z SSE. Uważam jednak, że gdybym sam pisał kod na arch. x86 64 mógłbym dodatkowo
zwiększyć szybkość wykorzystując dostępne wtedy dodatkowe 7 rejestrów xmm.
9

Podobne dokumenty