GPGPU General-Purpose computing on the GPU Piotr Stańczyk
GPGPU Brak wzrostu prędkości procesorów w ostatnim okresie Wprowadzanie technologii wielordzeniowej (Hyper Threading, Core Duo ) zrównoleglenie obliczeń sposobem zwiększenia mocy obliczeniowej Karty graficzne mają większą moc obliczeniową niż procesory (50GB / 8.5 GB, 240 / 25 GFLOPS) (NVIDIA GeForce 7900 GTX / dual-core 3.7 GHz Intel Pentium Extreme Edition 965) Coroczny wzrost mocy obliczeniowej kart graficznych kształtuje się na poziomie 1.7 2.3 razy (dla procesorów zwykło być 1.4) Każdy nowo-zakupiony komputer ma szybki GPU
GPGPU Dawniej karty graficzne pozwalały tylko na rysowanie trójkątów (teksturowane etc ) Z czasem wprowadzono możliwość programowalności: vertex-shader fragment-shader geometry-shader
CPU vs GPU CPU: Rozbudowana logika sterowania Optymalizacje wykonywanie kodu w innej kolejności wczesne przewidywanie zachować pętli GPU: Uproszczona logika (ulega zmianie) Obliczenia sekwencyjne w obrębie jednostek (odstępstwa w przypadku odwołań do tekstur - asynchroniczne) Główne problemy wydajnościowe pamięć. Rdzenie procesorów muszą synchronizować pamięć Karty graficzne stosują inne podejście
GPU flow control Predication ewaluowanie wszystkich gałęzi a następnie przypisanie wyniku z odpowiedniej gałęzi SIMD (Single Instruction Multiple Data) wszystkie jednostki wykonują te same instrukcje MIMD (Multi Instruction Multiple Data) jednostki niezależne od siebie (GeForce 6,7, NV-40, G70, Quadro)
CPU vs GPU: pamięć podręczna CPU - pamięć podręczna L1 / L2 rzędu megabajtów, GPU wykorzystywana do samplowania tekstur, wielkość rzędu kilkuset bajtów. CPU -> Read/Write, GPU -> Read Wykorzystanie GPU jest szczególnie efektywne w przypadku obliczeń wektorowych z sekwencyjnym dostępem do pamięci.
Dostęp p do pamięci - GeForce vs P4
GPU ograniczenia Brak prawdziwego stosu / nie można realizować rekurencji Brak zmiennych całkowitoliczbowych / operacji na bitach Brak typów wskaźnikowych Brak możliwości zapisu pamięci pod danym indeksem (adresem) Ciężko zastosować GPU do problemów niezwiązanych z grafiką Wszystko musi znaleźć się najpierw w pamięci GPU (problematyczne przeplatanie obliczeń GPU->CPU- >GPU) Pisząc dowolny algorytm z wykorzystaniem GPU musimy myśleć o nim przez pryzmat rysowania trójkątów
CPU vs GPU organizacja pamięci
CPU vs GPU scatter / gather scatter (rozproszenie) -> odpowiada wykonaniu operacji postaci a[i] = x gather (gromadzenie) -> odpowiada wykonaniu operacji postaci x = a[i] CPU pozwala wykonywać zarówno scatter jak i gather GPU nie wspomaga scatter (ze względu na efektywność / model pamięci podręcznej) gather na GPU można realizować przy użyciu odczytu z tekstury
Scatter -> Gather W wielu przypadkach można zmodyfikować algorytm w celu wyeliminowania scatter
GPU model obliczeń Vertex stream Wypełniany przez CPU (w nowych kartach graficznych też przez GPU) Fragment stream Generowany przez rasterizer, zawiera interpolowane wartości wierzchołków Frame buffer stream Wypełniany przez Fragment Processor, dane trafiają do Frame Buffer a. Nowe karty graficzne wspomagają do 16 buforów wynikowych Texture stream (1D, 2D, 3D, cube maps) Read only, Radom access, dostępne dla Fragment shaderów (i vertex shaderów w nowych kartach)
GPGPU jak używau ywać? Specjalistyczne języki programowania Cg (OpenGL) HLSL (DirectX) OpenGL Shading Language Podobne do C/C++ Ograniczenia wynikające z architektury GPU Udostępniają biblioteki matematyczne (normalizacja wektorów, interpolacje ) Wsparcie dla zmiennych całkowitoliczbowych Brook rozszerzenie ANSI C o obliczenia wektorowe mapowane automatycznie na GPU Ostatnio widziałem pewne moduły dla.net
Poszukiwanie maksimum macierzy log n razy wykonujemy następujące kroki: Umieszczamy macierz w teksturze Rysujemy kwadrat wykorzystując fragment-shader samplujący teksturę w czterech sąsiadujących polach i zwracający jako wynik największy element używamy bufora docelowego jako tekstury dla kolejnego przebiegu Czas działania O(max(log(n), n / p))
Sumy prefiksowe Podejście klasyczne Algorytm działający w czasie O(log(n)) Problem: - i-ta faza algorytmu wykonuje pracę O(n), z czego O(n/2 i ) jest użyteczne - karty graficzne nie posiadają n procesorów dla dowolnego n
Sumy prefiksowe Redukcja Zachowujemy wyniki częściowe Druga faza: Praca całkowita O(n), czas O(n/p)
Wspomaganie teselacji Proces polegający na generowaniu reprezentacji graficznej krzywych zadanych wzorami Wykorzystanie w systemach grafiki wektorowej (PDF, PS, OpenVG) Klasyczne podejście: wyznaczanie kolejnych punktów należących do krzywej, przybliżenie przy użyciu odcinków.
Wspomaganie teselacji Najczęściej stosowanymi krzywymi są łuki eliptyczne oraz krzywe Bezier (2 ego i 3 ego stopnia) Krzywa Bezier 2 stopnia: x(t) = A x (1-t)^2 + B x (1-t)*t + C x *t^2 y(t) = A y (1-t)^2 + B y (1-t)*t + C y *t^2
Wspomaganie teselacji Teselacja krzywej Bezier 2 ego stopnia: Podejście klasyczne (przybliżenie odcinkami) Wykorzystanie tekstury (zakładamy przekształcenia afiniczne krzywych) f(u,v) = u 2 v Podejście wymaga wygenerowania tekstury
Wspomaganie teselacji Podejście z teksturą nie działa dla krzywych 3 ego stopnia Można jednak wykorzystać fragment-combiner do wyliczenia przynależności każdego piksela do krzywej. Więcej informacji: http://research.microsoft.com/~cloop/loopblinn05.pdf
Diagramy Voronoi Dla danego zbioru punktów chcemy wyznaczyć dyskretny diagram Voronoi
Diagramy Voronoi
Diagramy Voronoi Zwiększenie dokładności wykonanie kroku o wielkości 1 dwukrotnie etc
Bump mapping vs. displacement mapping Bump mapping polega na dodaniu do tekstury dodatkowego kanału (wysokości), który pozwala na zwiększenie szczegółowości generowanych obrazów, bez zwiększenia liczby rysowanych trójkątów Problem: generowana geometria cały czas jest płaska
Bump mapping vs. displacement mapping Displacement mapping próba uzyskania realistycznej, trójwymiarowej tekstury (tak jak poprzednio wykorzystując dodatkowy kanał). Pomysł 1 wykorzysać vertex shader Problem, nie jesteśmy w stanie dodawać nowych wierzchołków, dostęp do tekstury jest ograniczony Pomysł 2 zastosować fragment shader Ekeftywny dostęp do tekstury, lecz brak możliwości generowania geometrii poza obszarem renderowanego trójkąta
Displacement mapping
Bump mapping vs. displacement mapping
Kompresja fraktalna Podział obrazu na obszary tej samej wielkości (n x n) Wyznaczenie domen (2n x 2n) Dla każdego obszaru wyznaczenie najbardziej podobnej domeny (downfiltered) z dokładnością do skali i przesunięcia APE100/Quadries SIMD (512 procesorów o mocy 25.6 GFLOPS), rysunek 256x256 w czasie 2 sekund PIV 3.2 GHz -> 280 sekund Geforce 6800 -> 1 sekunda
Sortowanie Brak możliwości wykonywania scatter konieczność korzystania z gather Do sortowania można stosować sieci sortujące Łączenie CPU-GPU daje najszybsze i najtańsze algorytmy do sortowania baz danych
Sortowanie sieci bitoniczne Do sortowania często stosowane są modyfikacje sieci bitonicznych
Sortowanie sieci bitoniczne
Sortowanie sieci bitoniczne Złożoność czasowa sortowania: Bitonic-sorter[n] -> O(log(n)) D(Sorter[n]) = D(Sorter[n/2]) + log(n) => O(log 2 (n)) Plus: teoretycznie możemy sortować dane w czasie O(log 2 (n)) Minus: Sieć składa się z O(n*log 2 (n)), co nie jest optymalne. W praktyce karta graficzna nie ma O(n) procesorów. Plus2: Istnieją sieci o wielkości O(n*log(n)) i głębokości O(log(n)) Minus2: Sieci te są bardzo skomplikowane i niepraktyczne
Sortowanie - modyfikacja Wykorzystując kartę graficzną mamy nieco więcej swobody... Symujacja opisanej sieci sortującej na maszynie 1-procesorowej zajmie O(n*log 2 (n)) Dokonajmy drobnych zmian w algorytmie
Sortowanie - modyfikacja Poniższy Half-cleaner dokonuje zamiany miejscami pierwszych lub ostatnich k elementów czyszczonych połówek. Wartość parametru k można wyznaczyć w czasie log(n) stosując wyszukiwanie binarne (e i i-ty element na wejściu): binarnie poszukujemy pozycji k dla której e k e k +n/2 zmienia znak (e k e k +n/2 ma przeciwny znak od e k +1 e k +n/2+1 Jeśli element e k > e k +n/2 to zamieniamy k = k pierwszych par w.p.p zamieniamy k = n k ostatnich par
Sortowanie - modyfikacje Wyznaczenie zamiany trwa O(log(n)) Zamiana trwa O(k) (pesymistycznie O(n)) Aby to poprawić, reprezentujemy wektor wejściowy w postaci pełnego drzewa binarnego + elementu wolnego e n
Sortowanie - modyfikacje Wyznaczanie wartości k + zamiana wygląda teraz tak: korzeń > element wolny? (a) Zamiana korzenia z elementem wolnym p <= lewy syn korzenia, q <= prawy Dla i = 1.. log(n) 1 p > q? Zamień p z q Jeśli (a) to zamień lewych synów p i q, wpp prawych Zastąp p i q odpowiednimi (lewymi bądź prawymi synami) Czas wykonania fazy half-clean wynosi O(log(n)) Utraciliśmy charakter sieci, redukując liczbę wszystkich operacji do O(n*log(n))
Sortowanie - zrównoleglenie Nasz algorytm wykonuje: log(n) poziomów scalania każdy scalanie składa się log(k) faz półczyszczenia każda faza półczyszczenia zawiera log(m) przejście po drzewie Sumaryczna praca to O(n*log(n)), zatem dla doskonałej maszyny p-procesorowej EREW możemy uzyskać czas O(n*log(n)/p) Dla karty graficznej mamy pewne ograniczenia
Sortowanie - zrównoleglenie Wszystkie procesory w karcie graficznej wykonują ten sam algorytm (z dokładnością do danych) W koszt czasowy wlicza się inicjalizacja strumieni Wszystkich strumieni jest log(n 3 ) Zatem posiadając p = o(n/log 2 (n)) procesorów możemy uzyskać złożoność czasową O(n*log(n) / p) Trzeba jeszcze obejść problemy z sekwencyjnością i brakiem możliwości wykonywania operacji scatter
GPGPU inne zastosowania Grafika, grafika, grafika Setki aplikacji dla obliczeń fizycznych Kompresja obrazu (metody fraktalowe) http://www.gpgpu.org/