• Nie Znaleziono Wyników

3. Równoległe przetwarzanie danych na procesorach graficznych

3.2. Technologia CUDA

3.2.1. Podstawowe pojęcia

Omawianie zagadnień związanych z architekturą procesorów graficznych, a tym bardziej modelu programowania, wymaga usystematyzowania pewnych pojęć. W dalszej części rozprawy pod terminem „proces” rozumiany będzie program, który aktualnie jest wykonywany, może nim być np. program użytkownika. Program sam z siebie nie jest procesem – jest jedynie zbiorem bitów zapisanych w pamięci, a dopiero uruchomienie go czyni go procesem. Wtedy przydzielany mu zostaje określony czas procesora i przestrzeń adresowa, w której może przechowywać swoje dane. W przypadku maszyny jednoprocesorowej w danym momencie przetwarzany może być tylko jeden proces, ale z uwagi na przełączanie kontekstu uzyskuje się wrażenie równoczesnej pracy wielu procesów.

Przełączenie kontekstu to wstrzymanie pracy jednego procesu i udostępnienie procesora innemu. Wymaga ono zapisania stanu przetwarzania aktualnego procesu i przywrócenia stanu

3 Równoległe przetwarzanie danych na procesorach graficznych

następnego procesu sprzed wywłaszczenia – polega więc na ustawieniu wartości rejestrów procesora do stanu takiego, w jakim się znajdowały przy wykonaniu ostatniej instrukcji.

Przełączanie kontekstu jest marnowaniem czasu z punktu widzenia zadań wykonywanych przez procesy, ponieważ żaden z nich w tym czasie nie jest wykonywany, a jedyną zaletą takiego rozwiązania jest zapewnienie współbieżności działania aplikacji.

W celu wykonania jakiegoś zadania procesy mogą tworzyć wątki. Są to lekkie procesy działające w tej samej wirtualnej przestrzeni adresowej, co tworzące je procesy ciężkie. Stan wątka jest zdefiniowany przez odrębną, mniejszą ilość danych (stos i własny stan rejestrów).

Wątek stanowi podstawową jednostkę wykorzystania procesora. Najważniejszą różnicą z punktu widzenia przetwarzania danych pomiędzy wątkiem a procesem jest to, że wątki współpracują ze sobą w celu wykonania danego zadania w ramach procesu, a procesy współzawodniczą ze sobą w dostępie o zasoby, takie jak np. czas procesora czy pamięć.

Jedną z najważniejszych kwestii związanych z programowaniem na GPU jest fakt, że proces wykorzystujący równoległe przetwarzanie danych na karcie graficznej musi zostać uruchomiony na procesorze głównym i z jego kodu odbywa się wywoływanie poszczególnych funkcji uruchamianych wielowątkowo na procesorze graficznym. Nie jest możliwe uruchomienie jakiejkolwiek analizy numerycznej na GPU bez utworzenia procesu sterującego nią z poziomu procesora głównego.

Tworzenie aplikacji wielowątkowych na procesor CPU znacznie różni się od implementacji funkcji wielowątkowych na procesor graficzny. W przypadku przetwarzania na procesorze głównym zwykle tworzone jest kilka wątków, które mogą skutecznie wykorzystać kilkurdzeniowy procesor – tworzenie większej liczby wątków i dzielenie na nie zadania jest nieefektywne ze względu na dodatkowy czas potrzebny na przełączenie kontekstu pomiędzy wątkami. W przypadku algorytmów uruchamianych na procesorze graficznym użytkownik ma do dyspozycji nawet 1 536 rdzeni (Tabela 3.1), które mogą w każdej chwili przetwarzać dokładnie tyle samo wątków. Ponadto architektura kart graficznych jest dostosowana do przetwarzania zadań dzielonych na miliony, a nawet miliardy wątków i to właśnie przy przetwarzaniu tak dużej ich liczby uzyskiwane są najwyższe wydajności obliczeń. Z tego względu przetwarzanie na GPU często określane jest mianem „masywnie równoległego”

(ang. massively parallel processing) [80, 85].

Bardzo ważnym pojęciem dotyczącym programowania na GPU jest osnowa (ang. warp). Jest to grupa zawsze 32 wątków (niezależnie od architektury), które wykonują kod w sposób zsynchronizowany, tzn. każdy wątek wchodzący w skład osnowy wykonuje w tym samym takcie zegara tę samą instrukcję na innych danych.

3 Równoległe przetwarzanie danych na procesorach graficznych 3.2.2. Architektura Fermi™

Procesor graficzny zbudowany jest z kilku multiprocesorów strumieniowych (Rys. 3.4).

Każdy taki multiprocesor składa się ze zgrupowanych w dwa moduły 32 rdzeni CUDA, które mogą przetwarzać dane w sposób równoległy. Architektura Fermi umożliwia umieszczenie do 16 multiprocesorów w procesorze GPU, a więc w całym procesorze może znajdować się do 512 rdzeni wykonujących obliczenia. W każdym z rdzeni umieszczona jest jedna jednostka arytmetyczno-logiczna (ALU, ang. arithmetic logic unit), umożliwiająca obliczenia na liczbach całkowitych, oraz jedna jednostka zmiennoprzecinkowa (FPU, ang. floating point unit).

W każdym multiprocesorze znajdują się cztery jednostki funkcji specjalnych (SFU, ang. Special Function Unit), na których wyznaczane są wartości funkcji matematycznych, takich jak: funkcje trygonometryczne, pierwiastkowanie, odwrotność itp. Układów tych jest ośmiokrotnie mniej niż rdzeni, dlatego wykonanie tych samych operacji dla 32 wątków (osnowy) wymaga kolejkowania wątków, jednak wyznaczanie wartości funkcji z ich pomocą jest zwykle szybsze niż obliczenie tej samej wartości na rdzeniach.

GPU

Rys. 3.4. Uproszczony schemat architektury procesora graficznego GF110 (Fermi):

MP – multiprocesor strumieniowy, SFU – jednostka funkcji specjalnych, I/O – jednostka zapisu i odczytu danych, R – rdzeń CUDA, FPU – jednostka zmiennoprzecinkowa, ALU – jednostka arytmetyczno-logiczna

MP

3 Równoległe przetwarzanie danych na procesorach graficznych

Multiprocesor strumieniowy posiada 16 jednostek wejścia i wyjścia, które pośredniczą w przekazywaniu danych pomiędzy różnymi typami pamięci. Układy te pozwalają na jednoczesne wyznaczanie adresów do zapisu bądź odczytu dla 16 wątków stanowiących połowę osnowy. W szczególności to właśnie przez nie odbywa się transfer danych do pamięci karty graficznej, która zlokalizowana jest poza procesorem graficznym, a jej rozmiar, typ i taktowanie zależą już od producenta karty graficznej.

W każdym multiprocesorze znajduje się kilka typów podręcznej pamięci o krótkim czasie dostępu i stosunkowo niedużych rozmiarach. Pamięć rejestrów jest w stanie pomieścić do 32 768 słów 32-bitowych dla wszystkich wątków posiadających status aktywny na danym multiprocesorze. Jest to najszybsza pamięć, w której każdy wątek może przechowywać swoje zmienne. Wymiana danych pomiędzy wątkami może odbywać się poprzez oddzielny moduł pamięci pozwalający na przechowanie w sumie 64 KB danych na multiprocesor. Obszar ten rozdzielony jest na pamięć wspólną (SM, ang. Shared Memory), umożliwiającą wspomnianą wymianę danych oraz pamięć podręczną pierwszego poziomu (L1 cache). Rozmiary obu tych pamięci mogą być konfigurowane w kombinacjach 48 KB i 16 KB lub 16 KB i 48 KB (Tabela 3.1).

Multiprocesor wyposażony jest również w dwa układy sterujące wywoływaniem instrukcji (ang. instruction dispatch unit), każdy z nich odpowiada za wykonywanie obliczeń na 16 zgrupowanych rdzeniach. Więcej rozważań na temat przetwarzania danych zostanie przedstawionych po omówieniu modelu programowania równoległego CUDA.

3.2.3. Programowanie wielowątkowe w CUDA C

Programowanie w języku CUDA C nierozłącznie związane jest z pracą na dwóch układach pozwalających na wykonywanie obliczeń numerycznych. Pierwszy z nich to host, którego głównymi elementami są procesor CPU (ang. Central Processing Unit) oraz pamięć operacyjna RAM. Procesor graficzny GPU wraz z wbudowaną pamięcią stanowi drugi układ, który w nomenklaturze języka CUDA C nazywany jest urządzeniem (ang. device). Z tego względu, przygotowany kod musi być kompilowany za pomocą dwóch kompilatorów:

podstawowego kompilatora języka C oraz kompilatora NVCC dostarczanego przez NVIDIĘ.

Ten ostatni zajmuje się przetworzeniem na kod maszynowy jedynie tych fragmentów programu, które przeznaczone są do wykonania w procesorze graficznym. Funkcje, które wywoływane są przez hosta, a ich wykonanie odbywa się w GPU w sposób równoległy przez wiele wątków, nazywane są funkcjami jądra (ang. kernel). Wszystkie wątki uruchomione za pomocą wywołania jednego jądra wykonują identyczny kod, a jedyną rozróżniającą je cechą jest unikatowy dla każdego wątka identyfikator jednoznacznie go określający.

W celu wygodnego zarządzania wątkami grupowane są one w macierze i bloki.

Podstawową grupą wątków jest blok (Rys. 3.5 a), w ramach którego mogą być one

3 Równoległe przetwarzanie danych na procesorach graficznych

zgrupowane w jedno-, dwu- lub trójwymiarową macierz. Jej maksymalne wymiary w architekturze Fermi mogą wynosić 1024 × 1024 × 64, jednak całkowita liczba wątków w bloku nie może przekroczyć 1024. Bloki grupowane są w siatkę (Rys. 3.5 b), która również może tworzyć maksymalnie trójwymiarową strukturę. Maksymalne wymiary macierzy tworzących siatkę bloków oraz każdy blok w zależności od potencjału obliczeniowego procesora przedstawione zostały w tabeli (Tabela 3.2 [22]). W tym miejscu pojawia się bardzo istotne pytanie: czy sposób podziału wątków w wyżej zdefiniowanej przestrzeni ma wpływ na wydajność? Odpowiedź oczywiście jest twierdząca. Można stwierdzić nawet więcej – tylko odpowiedni dla danego problemu podział wątków zapewnia uzyskanie maksymalnej wydajności przetwarzania danych.

Tabela 3.2. Maksymalna liczba wątków w zależności od architektury [23]

Potencjał obliczeniowy (Compute Capability) 1.X 2.X 3.X

Liczba wymiarów macierzy „siatka bloków” 2 3

Maksymalny rozmiar macierzy

„siatka bloków” dla danej współrzędnej

X 65535 231-1

Y 65535

Z - 65535

Liczba wymiarów macierzy „blok wątków” 3

Maksymalny rozmiar macierzy

„blok wątków” dla danej współrzędnej

X 512 1024

Y 512 1024

Z 64

Ograniczenia:

Maksymalna liczba wątków w bloku wątków 512 1024

Maksymalna liczba aktywnych wątków w

multiprocesorze 1024 1536 2048

Blok

Rys. 3.5. Organizacja wątków: blok wątków (a) oraz siatka bloków (b)

(a) (b)

3 Równoległe przetwarzanie danych na procesorach graficznych

Grupowanie wątków w bloki jest ważne z punktu widzenia przetwarzania ich na multiprocesorze – na każdy z nich wątki trafiają w „paczkach” będących blokami wątków, a żaden z nich nie może być dzielony na kilka multiprocesorów. Każdy multiprocesor przetwarza wątki w grupach składających się z 32 wątków – opisanych wcześniej osnowach.

W architekturze Fermi (Rys. 3.4, str. 63) w każdym multiprocesorze znajdują się 32 rdzenie.

Nie oznacza to jednak, że cała osnowa obliczana jest na wszystkich rdzeniach. Jak już zostało to wspomniane w rozdziale 3.2.2, rdzenie zgrupowane są w dwóch modułach po 16, a każdy z nich posiada oddzielny układ zarządzania wykonywaniem instrukcji, dlatego też w każdej chwili multiprocesor przetwarza dwie różne osnowy na 16 rdzeniach każdą, wykonując instrukcje wąktów należących do pierwszej i drugiej połowy osnowy naprzemiennie.

W wielu komentarzach dotyczących obliczeń na GPU można usłyszeć zarzut, że pamięć karty graficznej jest wolna. Stwierdzenie to wymaga pewnego komentarza.

Przepustowość głównych magistral w hoście oraz w układzie graficznym została przedstawiona na rysunku (Rys. 3.6, szerokość naszkicowanych magistral proporcjonalna jest do ich przepustowości). Wynika z niego, że magistrala łącząca globalną pamięć karty graficznej (ang. device memory)2 z procesorem graficznym stanowi najszybszy odcinek w drodze, którą pokonać muszą dane przesyłane pomiędzy pamięcią RAM komputera a procesorem graficznym. Jeżeli jednak spojrzymy na problem uwzględniając liczbę jednostek obliczeniowych (np. rdzeni), które daną magistralę muszą współdzielić, to w przypadku procesorów Intel Core i7 całe pasmo (a więc 26 GB/s) przypada tylko dla 4 – 6 rdzeni, natomiast w karcie graficznej 512 rdzeni procesora dzieli pomiędzy siebie magistralę oferującą transfery na poziomie 190 GB/s. W praktyce oznacza to dostępną efektywną przepustowość przypadającą na jeden rdzeń procesora graficznego o rząd wielkości mniejszą niż w przypadku procesora głównego.

2 rodzaje pamięci występujące w GPU omówione są szczegółowo w następnym podrozdziale.

GPU host

CPU Core i7

X58 Pamięć

operacyjna

~26 GB/s

~26 GB/s

Pamięć globalna karty

graficznej GPU

~8 GB/s

~190 GB/s

PCIe

Rys. 3.6. Przepustowość magistral w hoście i w karcie graficznej (szerokość naszkicowanych magistral proporcjonalna jest do ich przepustowości)

3 Równoległe przetwarzanie danych na procesorach graficznych Aby w jak najefektywniejszy sposób wykorzystać dostępną przepustowość magistrali oraz utrzymać optymalną wydajność obliczeń, niezbędne jest tworzenie jak największej liczby wątków. Odpowiednia architektura procesorów graficznych jest zaprojektowana właśnie pod tym kątem i dzięki niej przełączanie kontekstu pomiędzy wątkami zajmuje bardzo mało czasu. Dzieje się tak dlatego, że każdy multiprocesor ładuje i uruchamia tyle wątków, ile tylko jest możliwe ze względu na ograniczenia, takie jak rejestry czy pamięć wspólna. Dane powiązane z wątkiem raz umieszczone w pamięci rejestrów nigdy nie są przenoszone, nawet jeśli wątek oczekuje na dane. Multiprocesor zgodny z architekturą Fermi, jak pokazuje tabela (Tabela 3.2), może uruchomić maksymalnie 1 536 wątków.

Jeżeli każdy wątek wykorzystuje po 21 rejestrów 32-bitowych i jeśli nie ma innych ograniczeń, to taka właśnie liczba wątków otrzymuje status aktywnych i rozpoczyna się ich przetwarzanie na 32 rdzeniach w paczkach – osnowach. Dzięki temu, że cała pamięć każdego wątka znajduje się w multiprocesorze, przydzielanie czasu procesora innym aktywnym wątkom (przełączanie kontekstu) nie wymaga kopiowania danych i jest niemal natychmiastowe. Duża liczba uruchomionych wątków jest korzystna dla ogólnej wydajności obliczeń z uwagi na efektywne maskowanie dostępu do pamięci globalnej karty graficznej:

oczekiwanie na dostęp może być maskowane przez obliczenia, oczywiście pod warunkiem, że można je wykonać. W razie zapotrzebowania w danym zestawie wątków na dostęp (wymagający oczekiwania) do pamięci urządzenia, natychmiast zostaje przełączony kontekst na inny zestaw wątków i dane przetwarzane są bez przerwy. Przełączanie takie może odbywać się wielokrotnie, a praca na większych zbiorach wątków pozwala zapobiec sytuacji, w której rdzenie multiprocesora nie mogłyby kontynuować pracy, dopóki część wątków nie otrzyma danych, a tym samym ze względu na oczekiwanie zostałyby zmarnowane cykle zegara. Praca na większej liczbie wątków zwiększa wydajność całego procesu przetwarzania danych.

Zasada projektowania algorytmów w taki sposób, aby przetwarzanie odbywało się za pomocą jak największej liczby wątków, jest jak najbardziej poprawna w całym procesie tworzenia aplikacji, jednak kwestia odpowiedniego grupowania wątków w bloki i siatkę bloków ciągle pozostaje otwarta. To właśnie tutaj pojawia się najwięcej kompromisów i zależności, szczególnie dotyczących wykorzystania unikatowych typów pamięci o odpowiednich własnościach, a dodatkowo posiadających zwykle niewielki rozmiar.

3.2.4. Model pamięci

Karty graficzne zgodne z technologią CUDA posiadają sześć typów pamięci o różnych właściwościach, przeznaczonych do dowolnego wykorzystywania przez użytkownika. Zestawienie ich podstawowych parametrów i właściwości zostało przedstawione w tabeli (Tabela 3.3). Ostatnia kolumna zawiera subiektywną i zwięzłą charakterystykę przypisaną przez autora dla każdego rodzaju pamięci.

3 Równoległe przetwarzanie danych na procesorach graficznych

3.2.4.1. Rejestry

Rejestry to najwygodniejsza i najszybsza pamięć z punktu widzenia wykonywania operacji wewnątrz wątka. Pamięć ta, jak sama nazwa wskazuje, zorganizowana jest w 32-bitowe rejestry, a każdy multiprocesor (w architekturze Fermi) może zaalokować do 32 768 4-bajtowych słów dla wszystkich aktywnych wątków. Dostęp do każdego rejestru jest natychmiastowy (jeden cykl zegara), a czas życia każdej zmiennej zaalokowanej w rejestrach jest ograniczony czasem życia wątka.

3.2.4.2. Pamięć lokalna

Jest to niewielki (do kilkuset kilobajtów), zarezerwowany dla każdego multiprocesora obszar pamięci, fizycznie umiejscowiony w pamięci karty graficznej (a nie procesora). Dostęp do niej jest wolny i może zająć nawet do sześciuset cykli zegara. Słowo „lokalna” w nazwie związane jest jedynie z możliwością automatycznego umieszczenia w obszarze tej pamięci danych zadeklarowanych jako zmienne lokalne w kodzie funkcji jądra (wątka) w przypadku, gdy nie ma możliwości umieszczenia ich w pamięci rejestrów.

3.2.4.3. Pamięć wspólna

Ten typ pamięci wyróżnia się bardzo istotną i unikatową własnością. Dostęp do pamięci wspólnej jest możliwy ze wszystkich wątków współpracujących w ramach jednego bloku. Pamięć ta, podobnie jak rejestry, jest fizycznie umieszczona w procesorze graficznym, dzięki czemu dostęp do niej jest bardzo szybki. Umożliwia ona wymianę danych pomiędzy wątkami w trakcie ich działania. Jest to jedyny efektywny bufor pozwalający na takie

Tabela 3.3. Typy pamięci w technologii CUDA i ich właściwości

Typ pamięci Umiejscowienie Dostęp Czas życia Właściwości

Rejestry Chip GPU O/Z 1 wątek wątek mała, bardzo szybka

Globalna Karta graficzna O/Z wszystkie wątki + host

zarządzanie z hosta

bardzo duża (kilka GB), wolna

Stała Karta graficzna O wszystkie

wątki + host

Tekstur Karta graficzna O wszystkie wątki + host

zarządzanie z hosta

mała, z własną pamięcią podręczną

3 Równoległe przetwarzanie danych na procesorach graficznych

transfery. Maksymalny rozmiar pamięci wspólnej zależny jest od architektury GPU, jednak nie przekracza 48 KB na multiprocesor strumieniowy.

3.2.4.4. Pamięć globalna

Pamięć globalna jest największym obszarem przechowującym dane na karcie graficznej, a jej rozmiar zależy od producenta karty, zwykle jest on rzędu 2 GB, ale może być znacznie większy, np. 6 GB. Pamięć ta zlokalizowana jest poza procesorem graficznym, a czas dostępu do niej może sięgać do sześciuset cykli zegara. W urządzeniach zgodnych z potencjałem obliczeniowym 2.0 pamięć ta posiada własną pamięć podręczną, dzięki czemu wielokrotne odwoływanie się do danych z tego samego obszaru jest szybsze.

Jednym z najważniejszych czynników, mających wpływ na wydajność obliczeń na procesorach graficznych, jest poprawne zarządzanie przepływem danych przy odczycie i zapisie danych do pamięci globalnej. Dostęp do pamięci urządzenia może być realizowany jedynie przez 32-, 64-, lub 128-bajtowe segmenty danych. Jeżeli każda osnowa będzie w takich właśnie segmentach przesyłała dane pomiędzy zwartym obszarem pamięci w taki sposób, aby dane były kolejno po sobie odczytywane bądź zapisywane, to zapewniony zostanie tzw. łączony dostęp do pamięci (ang. coalesced memory access), który pozwoli osiągnąć maksymalne szybkości przekazywania danych. Każde odwołanie do danych, nawet jednego bajta spoza takiego segmentu (np. z poprzedzającego), powoduje zwielokrotnienie czasu transferu. (dla pamięci stałej i tekstur)

globalna

3 Równoległe przetwarzanie danych na procesorach graficznych 3.2.4.5. Pamięć stała

Jest to pamięć fizycznie zlokalizowana na karcie graficznej (poza procesorem GPU) o rozmiarze 64 KB, dostępna dla wszystkich wątków w trybie tylko do odczytu. We wszystkich wersjach procesorów graficznych posiada pamięć podręczną. Ważną cechą tej pamięci jest możliwość rozgłaszania danych (ang. broadcast). Jeżeli tą samą daną będą próbowały odczytać wątki stanowiące połowę osnowy (16 wątków działających w danym momencie na 16 zgrupowanych rdzeniach multiprocesora), operacja taka zajmie czas równy pojedynczej operacji odczytu przez jeden wątek. Ponadto pamięć ta jest buforowana i jeżeli druga połowa osnowy będzie potrzebowała tej samej wartości, operacja ta zostanie wykonana w pojedynczym cyklu zegara. W przypadku odczytu danych spod różnych adresów wszystkie operacje będą szeregowane.

3.2.4.6. Pamięć tekstur

Jest to pamięć o podobnych własnościach do pamięci stałej – jest umieszczona poza procesorem graficznym, w procesorze znajduje się jej pamięć podręczna, zapis do niej może odbywać się z hosta, natomiast wątki pracujące na GPU mogą ją jedynie odczytywać. Jak sama nazwa wskazuje, ten typ pamięci zaprojektowany został do przechowywania danych graficznych. Oczywiście jednak nic nie stoi na przeszkodzie, aby używać jej jako pamięci ogólnego przeznaczenia i wykorzystywać jej zalet. W układzie tym możemy przechowywać dane w postaci macierzy jedno-, dwu- lub trójwymiarowych. Co ważniejsze, pamięć ta zapewnia lokalność odczytu we wszystkich kierunkach macierzy. Znaczy to, że odczyt danych z macierzy w każdym kierunku (wierszami, kolumnami) będzie równie szybki, co nie jest możliwe do uzyskania w przypadku innych typów pamięci. Ponadto układ pamięci tekstur pozwala na automatyczne konwersje liczb całkowitych 8- i 16-bitowych stałoprzecinkowych na znormalizowane liczby zmiennoprzecinkowe z zakresów [0.0, 1.0] lub [-1.0, 1.0].

3.2.5. Narzędzia do optymalizacji kodu

W rozdziale 3.2.3 (str. 64) opisany został sposób przetwarzania wątków na multiprocesorach strumieniowych, umożliwiający niemal natychmiastowe przełączanie kontekstu. Najważniejszym warunkiem pozwalającym na efektywne wykorzystanie zalet tej metody jest przygotowywanie i uruchamianie wątków w taki sposób, aby jak najefektywniej wykorzystać zasoby sprzętowe procesora graficznego. We wspominanym rozdziale przeprowadzona była krótka analiza wykorzystania rejestrów; podobną analizę przeprowadzić można dla innych parametrów i ograniczeń, tj. rozmiaru pamięci wspólnej, maksymalnej liczby osnów na multiprocesor, maksymalnej liczby aktywnych wątków na multiprocesor, maksymalnej liczby bloków na multiprocesor itp. Oczywiście całą tę analizę można

3 Równoległe przetwarzanie danych na procesorach graficznych przeprowadzić na podstawie dokumentacji i analizy własnego kodu, jednak nie jest to najłatwiejszy sposób, głównie ze względu na mnogość parametrów.

Najdogodniejszym narzędziem umożliwiającym analizę powyższych ograniczeń, a w konsekwencji optymalizację kodu, jest dostarczany przez producenta (NVIDIA) CUDA Occupancy Calculator. Jest to program przygotowany w postaci arkusza kalkulacyjnego, który ułatwia projektowanie efektywnych algorytmów wielowątkowych na GPU w pełni wykorzystujących moc obliczeniową procesora graficznego. Użytkownik wprowadza jedynie pięć podstawowych informacji dotyczących implementowanego algorytmu, tj. liczbę wątków w bloku, liczbę wykorzystywanych rejestrów przez każdy wątek, rozmiar pamięci wspólnej wykorzystywanej przez blok wątków (shared memory) oraz konfigurację rozmiaru pamięci wspólnej i potencjał obliczeniowy karty. Na podstawie tych informacji oraz wbudowanego w arkusz zbioru parametrów procesorów graficznych generowane są takie informacje, jak liczba aktywnych wątków, osnów i bloków na multiprocesorze, co pozwala określić w jakim stopniu wykorzystywane są zasoby GPU. Analizę dodatkowo ułatwiają wykresy, z których bezpośrednio można odczytać, jaki wpływ na liczbę aktywnych osnów w multiprocesorze miałaby zmiana każdego z wprowadzanych parametrów.

Drugim, równie ważnym narzędziem do optymalizacji procedur implementowanych w języku CUDA C, jest NVIDIA Visual Profiler. Jest to aplikacja, która pozwala na analizę działania uruchomionego programu wykorzystującego procesor graficzny. Pozwala ona w szczególności zweryfikować rzeczywiste obciążenie procesora graficznego i wyznaczyć czasy działania poszczególnych procedur numerycznych oraz transferów danych. Szczegółowe analizy i wykorzystanie omawianych narzędzi przedstawione zostanie w kontekście implementowanych algorytmów.

Dodatkowym narzędziem wspierającym pracę jest NVIDIA Parallel Nsight – nakładka

Dodatkowym narzędziem wspierającym pracę jest NVIDIA Parallel Nsight – nakładka

Powiązane dokumenty