Programowanie kart graficznych. Architektura i API część 2

Podobne dokumenty
Wprowadzenie do programowania w środowisku CUDA. Środowisko CUDA

Programowanie kart graficznych

Wprowadzenie do programowania w środowisku CUDA. Środowisko CUDA

Programowanie kart graficznych. Architektura i API część 1

Programowanie procesorów graficznych GPGPU. Krzysztof Banaś Obliczenia równoległe 1

Programowanie CUDA informacje praktycznie i. Wersja

Programowanie Współbieżne

CUDA. cudniejsze przyk ady

Programowanie Równoległe wykład, CUDA, przykłady praktyczne 1. Maciej Matyka Instytut Fizyki Teoretycznej

CUDA obliczenia ogólnego przeznaczenia na mocno zrównoleglonym sprzęcie. W prezentacji wykorzystano materiały firmy NVIDIA (

Programowanie procesorów graficznych GPGPU. Krzysztof Banaś Obliczenia równoległe 1

Programowanie CUDA informacje praktycznie i przykłady. Wersja

Podstawy programowania. Wykład 6 Wskaźniki. Krzysztof Banaś Podstawy programowania 1

Podstawy programowania. Wykład Funkcje. Krzysztof Banaś Podstawy programowania 1

Programowanie procesorów graficznych NVIDIA (rdzenie CUDA) Wykład nr 1

Co to jest sterta? Sterta (ang. heap) to obszar pamięci udostępniany przez system operacyjny wszystkim działającym programom (procesom).

Programowanie PKG - informacje praktycznie i przykłady. Wersja z Opracował: Rafał Walkowiak

Struktura programu. Projekty złożone składają się zwykłe z różnych plików. Zawartość każdego pliku programista wyznacza zgodnie z jego przeznaczeniem.

Programowanie Równoległe Wykład, CUDA praktycznie 1. Maciej Matyka Instytut Fizyki Teoretycznej

Obliczenia na GPU w technologii CUDA

Programowanie kart graficznych

Programowanie Równoległe wykład 12. OpenGL + algorytm n ciał. Maciej Matyka Instytut Fizyki Teoretycznej

Porównanie wydajności CUDA i OpenCL na przykładzie równoległego algorytmu wyznaczania wartości funkcji celu dla problemu gniazdowego

Programowanie procesorów graficznych w CUDA.

Macierzowe algorytmy równoległe

Lab 9 Podstawy Programowania

Przygotowanie kilku wersji kodu zgodnie z wymogami wersji zadania,

METODY I JĘZYKI PROGRAMOWANIA PROGRAMOWANIE STRUKTURALNE. Wykład 02

Wskaźniki i dynamiczna alokacja pamięci. Spotkanie 4. Wskaźniki. Dynamiczna alokacja pamięci. Przykłady

Niezwykłe tablice Poznane typy danych pozwalają przechowywać pojedyncze liczby. Dzięki tablicom zgromadzimy wiele wartości w jednym miejscu.

Podstawy programowania komputerów

Stałe, tablice dynamiczne i wielowymiarowe

Programowanie w języku C++

1. Wartość, jaką odczytuje się z obszaru przydzielonego obiektowi to: a) I - wartość b) definicja obiektu c) typ oboektu d) p - wartość

utworz tworzącą w pamięci dynamicznej tablicę dwuwymiarową liczb rzeczywistych, a następnie zerującą jej wszystkie elementy,

Temat: Dynamiczne przydzielanie i zwalnianie pamięci. Struktura listy operacje wstawiania, wyszukiwania oraz usuwania danych.

// Liczy srednie w wierszach i kolumnach tablicy "dwuwymiarowej" // Elementy tablicy są generowane losowo #include <stdio.h> #include <stdlib.

Programowanie aplikacji równoległych i rozproszonych

Programowanie obiektowe W3

Wykład 1_2 Algorytmy sortowania tablic Sortowanie bąbelkowe

typ y y p y z łoż o on o e n - tab a lice c e w iel e owym m ar a o r we, e stru r kt k ury

Spis treści WSKAŹNIKI. DYNAMICZNY PRZYDZIAŁ PAMIĘCI W JĘZYKU C. Informatyka 2. Instrukcja do pracowni specjalistycznej z przedmiotu

Programowanie procesorów graficznych GPGPU

Programowanie procesorów graficznych GPGPU

Struktury czyli rekordy w C/C++

1 Podstawy c++ w pigułce.

Podstawy programowania w języku C++

Szablony klas, zastosowanie szablonów w programach

Strona główna. Strona tytułowa. Programowanie. Spis treści. Sobera Jolanta Strona 1 z 26. Powrót. Full Screen. Zamknij.

Podstawy programowania w języku C++

Informatyka I. Typy danych. Operacje arytmetyczne. Konwersje typów. Zmienne. Wczytywanie danych z klawiatury. dr hab. inż. Andrzej Czerepicki

Dynamiczny przydział pamięci w języku C. Dynamiczne struktury danych. dr inż. Jarosław Forenc. Metoda 1 (wektor N M-elementowy)

Stałe, znaki, łańcuchy znaków, wejście i wyjście sformatowane

Przetwarzanie Równoległe i Rozproszone

Tablicę 2-wymiarową można przedstawić jako pewien zestaw tablic 1-wymiarowych np.:

DYNAMICZNE PRZYDZIELANIE PAMIECI

CUDA ćwiczenia praktyczne

Tablice i struktury. czyli złożone typy danych. Programowanie Proceduralne 1

Dla każdej operacji łącznie tworzenia danych i zapisu ich do pliku przeprowadzić pomiar czasu wykonania polecenia. Wyniki przedstawić w tabelce.

Tablice. Jones Stygar na tropie zmiennych

Funkcje i tablice. Elwira Wachowicz. 23 maja 2013

1 Podstawy c++ w pigułce.

Podstawy programowania. Wykład 7 Tablice wielowymiarowe, SOA, AOS, itp. Krzysztof Banaś Podstawy programowania 1

PARADYGMATY PROGRAMOWANIA Wykład 4

LABORATORIUM 3 ALGORYTMY OBLICZENIOWE W ELEKTRONICE I TELEKOMUNIKACJI. Wprowadzenie do środowiska Matlab

Język C zajęcia nr 11. Funkcje

Programowanie Współbieżne. Algorytmy

ZASADY PROGRAMOWANIA KOMPUTERÓW

Podstawy programowania w języku C++

do instrukcja while (wyrażenie);

Lab 8. Tablice liczbowe cd,. Operacje macierzowo-wektorowe, memcpy, memmove, memset. Wyrażenie warunkowe.

CUDA PROGRAMOWANIE PIERWSZE PROSTE PRZYKŁADY RÓWNOLEGŁE. Michał Bieńkowski Katarzyna Lewenda

Języki programowania. Przetwarzanie plików amorficznych Konwencja języka C. Część siódma. Autorzy Tomasz Xięski Roman Simiński

Programowanie dynamiczne

IX. Wskaźniki.(3 godz.)

JCuda Czy Java i CUDA mogą się polubić? Konrad Szałkowski

Struktury. Przykład W8_1

Zmienne, stałe i operatory

Podstawy programowania w języku C++

2 Przygotował: mgr inż. Maciej Lasota

Moc płynąca z kart graficznych

Wstęp do programowania INP001213Wcl rok akademicki 2017/18 semestr zimowy. Wykład 8. Karol Tarnowski A-1 p.

/* dołączenie pliku nagłówkowego zawierającego deklaracje symboli dla wykorzystywanego mikrokontrolera */ #include <aduc834.h>

C++ - przeciążanie operatorów. C++ - przeciążanie operatorów. C++ - przeciążanie operatorów. C++ - przeciążanie operatorów

Języki programowania C i C++ Wykład: Typy zmiennych c.d. Operatory Funkcje. dr Artur Bartoszewski - Języki C i C++, sem.

Wskaźniki w C. Anna Gogolińska

Jacek Matulewski - Fizyk zajmujący się na co dzień optyką kwantową i układami nieuporządkowanymi na Wydziale Fizyki, Astronomii i Informatyki

Wstęp do Programowania, laboratorium 02

Podstawy programowania w języku C++

MACIERZE. Sobiesiak Łukasz Wilczyńska Małgorzata

Ćwiczenie 4. Obsługa plików. Laboratorium Podstaw Informatyki. Kierunek Elektrotechnika. Laboratorium Podstaw Informatyki Strona 1.

Tablice (jedno i wielowymiarowe), łańcuchy znaków

JĘZYKI PROGRAMOWANIA Z PROGRAMOWANIEM OBIEKTOWYM. Wykład 6

Obsługa plików. Laboratorium Podstaw Informatyki. Kierunek Elektrotechnika. Laboratorium Podstaw Informatyki Strona 1. Kraków 2013

Języki i metodyka programowania. Wskaźniki i tablice.

Języki C i C++ Wykład: 2. Wstęp Instrukcje sterujące. dr Artur Bartoszewski - Języki C i C++, sem. 1I- WYKŁAD

// Potrzebne do memset oraz memcpy, czyli kopiowania bloków

Techniki programowania INP001002Wl rok akademicki 2018/19 semestr letni. Wykład 3. Karol Tarnowski A-1 p.

W języku C/C++ pomiędzy wskaźnikami a tablicami istnieje bardzo ścisły związek. Do onumerowania elementów w tablicy służą tzw. INDEKSY.

Dodatkowo klasa powinna mieć destruktor zwalniający pamięć.

Transkrypt:

Programowanie kart graficznych Architektura i API część 2

CUDA hierarchia pamięci c.d.

Globalna pamięć urządzenia: funkcje CUDA API takie jak cudamalloc() i cudafree() z założenia służą do manipulowania pamięcią adresowaną liniowo (czyli predystynowaną do przechowywania i obróbki wektorów) nie wyklucza to użycia tych funkcji do obróbki tablic dwu- i trójwymiarowych, ale dla tych zastosowań CUDA API przewiduje użycie funkcji specjalizowanych są to: cudamallocpitch() - dla macierzy 2D cudamalloc3d() - dla macierzy 3D

Globalna pamięć urządzenia: użycie tych funkcji gwarantuje, że tablica zostanie rozmieszczona w pamięci w optymalny sposób, gwarantujący najkrótszy czas dostępu do wierszy oraz przy kopiowaniu jej elementów do innych regionów pamięci urządzenia najpierw rozpatrzymy aspekt 2D takiego przydziału

CUDA API zarządzanie pamięcią: przydzielenie pamięci dla tablicy 2D cudaerror_t cudamallocpitch( void **devptr, size_t *pitch, size_t width, size_t height ); Funkcja przydziela co najmniej width*height bajtów i zwraca wskaźnik do przydzielonej pamięci w zmiennej *devptr; funkcja może dopełnić każdy z wierszy dodatkowymi bajtami celem optymalnego rozmieszczenia danych; rzeczywisty rozmiar wiersza jest zwracany w pitch.

cudamallocpitch: parametry: devptr: wskaźnik na wskaźnik reprezentujący przydzielony blok pamięci pitch : wskaźnik na daną reprezentującą przydzielony faktycznie rozmiar wiersza width : rozmiar wiersza przydzielanej tablicy (liczba bajtów) height : rozmiar kolumny przydzielanej tablicy (liczba wierszy) wynik: cudasuccess poprawne zakończenie działania funkcji cudaerrormemoryallocation błąd przydziału, awaryjny powrót z funkcji

cudamallocpitch przykład (iterowanie po elementach tablicy 2D) float *devptr; int pitch; cudamallocpitch((void**)&devptr, &pitch,width * sizeof(float), height); MyKernel<<<100, 512>>>(devPtr, pitch); global void MyKernel(float* devptr, int pitch) { for (int r = 0; r < height; ++r) { // wyliczenie adresu początkowego wiersza float *row = (float *)((char *)devptr + r * pitch); } } // iterowanie po elementach wiersza for (int c = 0; c < width; ++c) { float element = row[c]; }

Pamięć urządzenia: a teraz pora na aspekt 3D

CUDA API zarządzanie pamięcią: reprezentowanie rozmiarów tablicy 3D struct cudaextent { size_t depth; // głębokość (liczba elementów) size_t height;// wysokość (liczba elementów) size_t width; // szerokość (liczba bajtów(!)) };

CUDA API zarządzanie pamięcią: wytworzenie struktury typu cudaextent: struct cudaextent make_cudaextent(size_t w, size_t h, size_t d); parametry: w : szerokość definiowanej tablicy (liczba bajtów) h : wysokość definiowanej tablicy (liczba elementów) d : głębokość definiowanej tablicy (liczba elementów) wynik: nowo utworzona struktura typu cudaextent wypełniona podanymi danymi

CUDA API zarządzanie pamięcią: reprezentowanie przydziału tablicy 3D struct cudapitchedptr { size_t pitch; // rozmiar wiersza (liczba bajtów) void ptr; // wskaźnik na przydzieloną pamięć }; size_t xsize; // logiczna szerokość przydziału // (liczba elementów) size_t ysize; // logiczna wysokość przydziału // (liczba elementów)

CUDA API zarządzanie pamięcią: wytworzenie struktury typu cudapitchedptr: struct cudapitchedptr make_cudapitchedptr (void *d, size_t p, size_t xsz, size_t ysz); parametry: d : wskaźnik na przydzieloną tablicę p : rozmiar wiersza fizycznego (liczba bajtów) h : logiczna szerokość tablicy (liczba elementów) d : logiczna głębokość tablicy (liczba elementów) wynik: nowo utworzona struktura typu cudapitchedptr wypełniona podanymi danymi

CUDA API zarządzanie pamięcią: przydzielenie pamięci dla tablicy 3D cudaerror_t cudamalloc3d( struct cudapitchedptr *pitcheddevptr, struct cudaextent extent ); Funkcja przydziela co najmniej width*height*depth bajtów i wypełnia danymi strukturę wskazywaną przez *pitcheddevptr; funkcja może dopełnić każdy z wierszy dodatkowymi bajtami celem optymalnego rozmieszczenia danych; rzeczywisty rozmiar wiersza jest zwracany w polu pitch.

cudamallocpitch: parametry: pitcheddevptr : wskaźnik na strukturę przeznaczoną do reprezentowania przydzielonego bloku pamięci (zostanie wypełniona danymi przez funkcję) extent: struktura opisująca rozmiary przydzielanej wynik: tablicy cudasuccess poprawne zakończenie działania funkcji cudaerrormemoryallocation błąd przydziału, awaryjny powrót z funkcji

cudamalloc3d przykład (iterowanie po elementach tablicy 3D) CudaPitchedPtr devpitchedptr; CudaExtent extent = make_cudaextent(64, 64, 64); cudamalloc3d(&devpitchedptr, extent); MyKernel<<<100, 512>>>(devPitchedPtr, extent); global void MyKernel(cudaPitchedPtr devpitchedptr, cudaextent extent) { char* devptr = devpitchedptr.ptr; size_t pitch = devpitchedptr.pitch; size_t slicepitch = pitch * extent.height; } for(int z = 0; z < extent.depth; ++z) { //wyliczenie adresu początku plasterka char* slice = devptr + z * slicepitch; for(int y = 0; y < extent.height; ++y) { // wyliczenie adresu początku wiersza float *row = (float *)(slice + y * pitch); // iterowanie po wierszu for(int x = 0; x < extent.width; ++x) { float element = row[x]; } } }

Dzielona pamięć urządzenia: użycie pamięci dzielonej urządzenia jest w kodzie źródłowym manifestowane użyciem specyfikatora shared zmienna zadeklarowana jako shared jest dostępna we wszystkich wątkach i w każdym z nich oznacza ten sam byt pamięć dzielona jest (według deklaracji producenta) znacząco szybsza (i w odczycie i w zapisie) niż pamięć globalna daje to szansę przyspieszania obliczeń wymagających intensywnego dostępu do pamięci

Dzielona pamięć urządzenia: zilustrujemy to przykładem opartym o rutynowe mnożenie dwóch macierzy każdy wątek ma za zadanie odczytać jeden wiersz macierzy A i jedną kolumnę macierzy B w celu wyliczenia odpowiedniego elementu macierzy wynikowej C

Schemat pracy algorytmu: Źródło: NVIDIA CUDA Programming Guide Version 3.0

Mnożenie macierzy implementacja klasyczna część 1/4 // Macierze są pamiętane wierszami, a więc: // M(row, col) = *(M.elements + row * M.width + col) typedef struct { int width; int height; float *elements; } Matrix; // definiujemy rozmiar bloku wątków: #define BLOCK_SIZE 16 // prototyp funkcji mnożącej (kernela) global void MatMulKernel(const Matrix, const Matrix, Matrix); // Zakładamy (dla uproszczenia rozważań), że wymiary macierzy są // całkowitymi wielokrotnościami wartości BLOCK_SIZE

Mnożenie macierzy implementacja klasyczna część 2/4 // Funkcja mnożąca void MatMul(const Matrix A, const Matrix B, Matrix C) { // kopiujemy macierze A i B to globalnej pamięci urządzenia // najpierw A Matrix d_a; d_a.width = A.width; d_a.height = A.height; size_t size = A.width * A.height * sizeof(float); cudamalloc((void **)&d_a.elements, size); cudamemcpy(d_a.elements, A.elements, size, cudamemcpyhosttodevice); // potem B Matrix d_b; d_b.width = B.width; d_b.height = B.height; size = B.width * B.height * sizeof(float); cudamalloc((void **)&d_b.elements, size); cudamemcpy(d_b.elements, B.elements, size,cudamemcpyhosttodevice); : :

Mnożenie macierzy implementacja klasyczna część 3/4 : : // przydzielamy macierz C w globalnej pamięci urządzenia Matrix d_c; d_c.width = C.width; d_c.height = C.height; size = C.width * C.height * sizeof(float); cudamalloc((void **)&d_c.elements, size); // preparujemy środowisko i wywołujemy kernel dim3 dimblock(block_size, BLOCK_SIZE); dim3 dimgrid(b.width / dimblock.x, A.height / dimblock.y); MatMulKernel<<<dimGrid, dimblock>>>(d_a, d_b, d_c); // odbieramy obliczoną macierz C z pamięci globalnej urządzenia cudamemcpy(c.elements, d_c.elements, size, cudamemcpydevicetohost); } // zwalniamy pamięć cudafree(d_a.elements); cudafree(d_b.elements); cudafree(d_c.elements);

Mnożenie macierzy implementacja klasyczna część 4/4 // kernel odpowiedzialny za wymnożenie macierzy global void MatMulKernel(Matrix A, Matrix B, Matrix C) { } // każdy wątek oblicza jeden element macierzy C // akumulując wynik w zmiennej Cvalue float Cvalue = 0; int row = blockidx.y * blockdim.y + threadidx.y; int col = blockidx.x * blockdim.x + threadidx.x; for (int e = 0; e < A.width; ++e) Cvalue += A.elements[row * A.width + e] * B.elements[e * B.width + col]; C.elements[row * C.width + col] = Cvalue;

A teraz to samo, ale z użyciem pamięci dzielonej... zakładamy, że każdy blok wątków jest odpowiedzialny za wyliczenie kwadratowej podmacierzy macierzy C, którą nazwiemy tu C sub w ten sposób każde wydzielone C sub jest iloczynem dwóch prostokątnych podmacierzy: macierzy A o wymiarach (A.width, n*block_size), która ma te same numery wierszy co C sub macierzy B o wymiarach (n*block_size, B.heigth), która ma te same numery kolumn co C sub każdą z powyższych prostokątnych macierzy dzieli się na odpowiednią liczbę kwadratowych podpodmacierzy o boku BLOCK_SIZE

Schemat pracy algorytmu: Źródło: NVIDIA CUDA Programming Guide Version 3.0

w ten sposób każdy z elementów podmacierzy C sub wyliczany jako suma iloczynów wyróżnionych podpodmacierzy każdy z iloczynów jest obliczany drogą wstępnego załadowania odpowiednich fragmentów macierzy A i B z pamięci globalnej do pamięci dzielonej zakładamy, że w pierwszej kolejności każdy wątek ładuje swoje dane, a następnie wylicza cząstkową wartość iloczynu każdy wątek akumuluje wartość cząstkową, aby na końcu zapisać ją w pamięci globalnej grupując obliczenia w powyższy sposób zmniejszamy transfer z pamięci globalnej, ponieważ macierz A jest czytana jedynie (B.width / block_size) razy, a macierz B (A.height / block_size) razy

Przy okazji zawieramy znajomość z nowym specyfikatorem: device poprzedza deklaracje funkcji funkcja zadeklarowana w taki sposób przeznaczona jest do wykonania na urządzeniu (tylko i wyłącznie) funkcję taką można wywołać z kodu wykonywanego na urządzeniu (tylko i wyłącznie)

Mnożenie macierzy implementacja z pamięcią dzieloną część 1/6 // Macierze są pamiętane wierszami, a więc: // M(row, col) = *(M.elements + row * M.width + col) typedef struct { int width; int height; int stride; float *elements; } Matrix; // funkcja do odczytywania wartości elementu wskazanej macierzy device float GetElement(const Matrix A, int row, int col) { return A.elements[row * A.stride + col]; } // funkcja do zapisywania wartości elementu wskazanej macierzy device void SetElement(Matrix A, int row, int col, float value) { A.elements[row * A.stride + col] = value; }

Mnożenie macierzy implementacja z pamięcią dzieloną część 2/6 // wykreowanie opisu podmacierzy o rozmiarze BLOCK_SIZExBLOCK_SIZE, która // ulokowana jest col podmacierzy w prawo i row podmacierzy w dół // licząc od lewego wierzchołka danej macierzy device Matrix GetSubMatrix(Matrix A, int row, int col) { Matrix Asub; Asub.width = BLOCK_SIZE; Asub.height = BLOCK_SIZE; Asub.stride = A.stride; Asub.elements = &A.elements[A.stride * BLOCK_SIZE * row+ BLOCK_SIZE * col]; } return Asub;

Mnożenie macierzy implementacja z pamięcią dzieloną część 3/6 #define BLOCK_SIZE 16 global void MatMulKernel(const Matrix, const Matrix, Matrix); void MatMul(const Matrix A, const Matrix B, Matrix C) { Matrix d_a; d_a.width = d_a.stride = A.width; d_a.height = A.height; size_t size = A.width * A.height * sizeof(float); cudamalloc((void **)&d_a.elements, size); cudamemcpy(d_a.elements, A.elements, size,cudamemcpyhosttodevice); Matrix d_b; d_b.width = d_b.stride = B.width; d_b.height = B.height; size = B.width * B.height * sizeof(float); cudamalloc((void **)&d_b.elements, size); cudamemcpy(d_b.elements, B.elements, size,cudamemcpyhosttodevice); : :

Mnożenie macierzy implementacja z pamięcią dzieloną część 4/6 : : Matrix d_c; d_c.width = d_c.stride = C.width; d_c.height = C.height; size = C.width * C.height * sizeof(float); cudamalloc((void **)&d_c.elements, size); dim3 dimblock(block_size, BLOCK_SIZE); dim3 dimgrid(b.width / dimblock.x, A.height / dimblock.y); MatMulKernel<<<dimGrid, dimblock>>>(d_a, d_b, d_c); cudamemcpy(c.elements, d_c.elements, size, cudamemcpydevicetohost); } cudafree(d_a.elements); cudafree(d_b.elements); cudafree(d_c.elements);

Mnożenie macierzy implementacja z pamięcią dzieloną część 5/6 global void MatMulKernel(Matrix A, Matrix B, Matrix C) { // ustalenie numeru wiersza i kolumny wewnątrz bloku int blockrow = blockidx.y; int blockcol = blockidx.x; // każdy blok oblicza jedną podmacierz Csub macierzy C Matrix Csub = GetSubMatrix(C, blockrow, blockcol); // każdy wątek oblicza jeden element Csub akumulując wynik w Cvalue float Cvalue = 0; // ustalenie numeru wiersza i kolumny wewnątrz wątku int row = threadidx.y; int col = threadidx.x; : :

Mnożenie macierzy implementacja z pamięcią dzieloną część 6/6 } // iterujemy wszystkie podmacierze A i B, które // są potrzebne do obliczenia Csub mnożymy ze sobą każdą parę // podmacierzy i akumulujemy wynik for(int m = 0; m < (A.width / BLOCK_SIZE); ++m) { // kreujemy podmacierz Asub macierzy A Matrix Asub = GetSubMatrix(A, blockrow, m); // kreujemy podmacierz Bsub macierzy B Matrix Bsub = GetSubMatrix(B, m, blockcol); // deklarujemy obszar pamięci dzielonej dla podmacierzy Asub i Bsub shared float As[BLOCK_SIZE][BLOCK_SIZE]; shared float Bs[BLOCK_SIZE][BLOCK_SIZE]; // załaduj Asub i Bsub z pamięci globalnej do dzielonej // (każdy wątek ładuje jeden element z każdej podmacierzy) As[row][col] = GetElement(Asub, row, col); Bs[row][col] = GetElement(Bsub, row, col); // poczekajmy, aż wszystkie dane zostaną skopiowane syncthreads(); // mnożymy Asub i Bsub for (int e = 0; e < BLOCK_SIZE; ++e) Cvalue += As[row][e] * Bs[e][col]; // poczekajmy, aż obliczenia zostaną zakończone zanim zabierzemy // się za przetwarzanie następnej podmacierzy syncthreads(); } // odsyłamy obliczone Cvalue do pamięci urządzenia SetElement(Csub, row, col, Cvalue);

Wiele urządzeń CUDA w jednym systemie hosta: jeden host może zarządzać więcej niż jednym urządzeniem CUDA urządzenia te mogą być wyliczane i odpytywane o właściwości jedno z nich może być wybierane do wykonywania kodu kernela więcej niż jeden wątek hosta może wykonywać kod kernela na jednym urządzeniu CUDA jeden wątek hosta może wykonywać kod kernela tylko na jednym urządzeniu zasoby wykreowane i używane w jednym wątku hosta nie mogą być używane w innych wątkach

Wiele urządzeń CUDA w jednym systemie hosta: domyślnie jako urządzenie aktywne wybierane jest urządzenie o numerze 0 (pierwsze rozpoznane w systemie) ustalenie innego aktywnego urządzenia jest możliwy poprzez wywołanie funkcji cudasetdevice() zmiana aktywnego urządzenia jest możliwa pod warunkiem wykonania funkcji cudathreadexit(), które sygnalizuje zakończenie współpracy z dotychczasowym aktywnym urządzeniem

CUDA API zarządzanie urządzeniami: wybór aktywnego urządzenia cudaerror_t cudasetdevice (int device) parametry: device: numer urządzenia CUDA wybieranego do wykonywania kodu kerneli (uwaga urządzenia numeruje się od zera!) wynik: cudasuccess wykonanie poprawne cudaerrorinvaliddevice niepoprawne urządzenie cudaerrorsetonactiveprocess - próba użycia funkcji w sytuacji, gdy urządzenie jest już wybrane i wykonuje kod

CUDA API zarządzanie urządzeniami: zwolnienie wszystkich zasobów na aktywnym urządzeniu cudaerror_t cudathreadexit (void) wynik: cudasuccess wykonanie poprawne