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