to get the file
Transkrypt
to get the file
Instrukcja 6 1 Współczesne procesory graficzne Temat: Wstęp do programowania w CUDA Przygotował: mgr inż. Tomasz Michno Wstęp Programowanie z użyciem CUDA różni się nieco od programowania z użyciem OpenCL. Pod pewnymi względami jest ono dużo prostsze, ponieważ wiele operacji niezbędnych do zaprogramowania w OpenCL wykonuje za nas kompilator dostarczony wraz z SDK. W przeciwieństwie do OpenCL, cały kod programu, zarówno dla hosta (dla CPU), jak i dla karty graficznej (dla GPU) znajduje się w jednym pliku o rozszerzeniu .cu. Funkcję główną kernela należy oznaczyć przedrostkiem __global__, natomiast funkcje pomocnicze (oraz wszelkie zmienne po stronie karty graficznej) przedrostkiem __device__. Wszystkie pozostałe zmienne i funkcje bez tych przedrostków są uznawane za znajdujące się po stronie hosta. Przykładowy kod kernela przepisującego elementy jednej tablicy do drugiej może wyglądać następująco: 1 2 3 4 5 6 __global__ v o i d mojKernel ( c o n s t f l o a t ∗ we , f l o a t ∗ wy , i n t N) { i n t i = blockDim . x ∗ b l o c k I d x . x + t h r e a d I d x . x ; i f ( i < N) wy [ i ]=we [ i ] ; } Widać tutaj pewne różnice w porównaniu z kodem w OpenCL, jednak ogólnie kod wygląda podobnie. Pierwszą różnicą jest sam przedrostek głównej funkcji kernela - zamiast __kernel__ używany jest przedrostek __global__, który oznacza, że do tej funkcji będzie możliwy dostęp z kodu hosta (w celu uruchomienia). Kolejna różnica to brak przedrostków przy wskaźnikach w parametrach - domyślnie są one traktowane jako wskaźniki odwołujące się do pamięci karty graficznej. Ostatnia różnica to nieco bardziej skomplikowane pobieranie globalnego identyfikatora work-item’a (linia nr 3). W C for CUDA nie ma funkcji get_global_id(), takiej jak w OpenCL. Identyfikator należy wyliczać ręcznie. W przypadku prostych programów mogłoby często wystarczyć od1 czytanie wartości threadIdx.x, jednak bezpieczniej jest zawsze stosować pełne obliczanie identyfikatora (takie jak w linii nr 3). Kod hosta, potrzebny do utworzenia buforów, przesłania danych i uruchomienia kernela jest znacznie prostszy niż w języku OpenCL, co można zauważyć na poniższym przykładzie: 1 2 cudaMalloc ( ( v o i d ∗ ∗ )&d_we , s i z e ) ; cudaMalloc ( ( v o i d ∗ ∗ )&d_wy , s i z e ) ; 3 4 5 int threadsPerBlock = 256; i n t b l o c k s P e r G r i d = (ROZMIAR_TABLICY + t h r e a d s P e r B l o c k − 1 ) / threadsPerBlock ; 6 7 cudaMemcpy (d_we , h_we , s i z e , cudaMemcpyHostToDevice ) ; 8 9 mojKernel<<<b l o c k s P e r G r i d , t h r e a d s P e r B l o c k >>>(d_we , d_wy, N) ; 10 11 cudaMemcpy (h_wy , d_wy , s i z e , cudaMemcpyDeviceToHost ) ; Funkcje do tworzenia i przesyłania danych są odpowiednikami funkcji z języka C. Do tworzenia buforów po stronie karty graficznej (linie 1 i 2) używana jest funkcja cudaMalloc(), która jako pierwszy parametr przyjmuje wskaźnik na wskaźnik, który będzie przechowywał adres bufora w pamięci GPU oraz rozmiar przydzielonej pamięci. Z kolei zwalnianie pamięci przydzielonej dla tego bufora można wykonać wywołując funkcję cudaFree(), która jako parametr przyjmuje wskaźnik na ten bufor. Przesyłanie danych pomiędzy pamięciami odbywa się bardzo podobnie jak w języku C - używając funkcji cudaMemcpy(). Jej parametry są następujące: 1. wskaźnik na obszar pamięci, gdzie zostaną skopiowane dane, 2. wskaźnik na pamięć, z której będą kopiowane dane, 3. rozmiar kopiowanego obszaru, 4. flaga informująca, jakie ma zostać wykonane kopiowanie: cudaMemcpyHostToDevice - kopiowanie z pamięci głównej do pamięci karty graficznej cudaMemcpyDeviceToHost - kopiowanie z pamięci karty graficznej do pamięci głównej Uruchamianie kernela jest również dużo prostsze niż w OpenCL i w dużej mierze przypomina wywoływanie zwykłej funkcji (linia nr 9) - podajemy nazwę 2 kernela, pomiędzy znakami «< oraz »> określamy ile ma być uruchomionych work-item’ów. Potem, tak jak przy zwykłej funkcji w nawiasach powinny znaleźć się parametry. 2 Zadanie Napisać program/programy, które będą wykonywały operacje: mnożenia i dzielenia wektora (tablicy) przez skalar (liczbę), dodawania wektorów, odejmowania wektorów. Programy powinny wykonywać obliczenia na karcie graficznej. 3