Sunteți pe pagina 1din 10

2.

Fire de execu ie CUDA


2.1. Organizarea firelor de execu ie
Paralelismul detaliat al datelor i în consecin al firelor de execu ie reprezint baza execu iei paralele în
CUDA.
Deoarece toate firele dintr-un grid execut aceea i func ie a kernel-ului, acestea se bazeaz pe coordonate
unice care permit firelor s se deosebeasc unele de altele i astfel s identifice i por iunile corespunz toare
de date pe care trebuie s le proceseze. Firele sunt organizate într-o ierarhie cu dou nivele folosind dou
coordonate unice: blockIdx (indice al blocului în care se afl firul) i threadIdx (indice al firului), ale
ror valori sunt asociate de c tre sistemul de execu ie CUDA. Variabilele blockIdx i threadIdx sunt
variabile predefinite i preini ializate, care pot fi accesate în interiorul func iei kernel-ului.
Când un fir execut func ia kernel-ului, referin ele la variabilele blockIdx i threadIdx returneaz
coordonatele firului. Variabile predefinite adi ionale, gridDim i blockDim, furnizeaz dimensiunea
grid-ului precum i dimensiunea fiec rui bloc din grid.
Fig. 2.1 ilustreaz un exemplu simplu pentru organizarea CUDA a firelor. Grid-ul const din N blocuri de
fire, fiecare având o valoare blockIdx.x între 0 i N-1. Fiecare bloc la rândul lui const din M fire de
execu ie, fiecare o valoare threadIdx.x între 0 i M-1. La nivelul grid-ului blocurile sunt organizate
într-o structur unidimensional , toate firele dintr-un bloc fiind la rândul lor organizate tot într-o structur
unidimensional . Prin urmare grid-ul const în total din N*M fire de execu ie.
Zona neagr a fiec rui bloc de fire din figur ilustreaz o por iune a codului kernel-ului. Codul utilizeaz
valoarea threadID = blockIdx.x * blockDim.x + threadIdx pentru a identifica por iunea
din datele de intrare pe care trebuie s o citeasc precum i por iunea din datele de ie ire unde trebuie s
stocheze rezultatul. De exemplu, pentru firul 3 din blocul 0 variabila threadId va avea valoarea
0*M+3=3.
Se presupune c un grid are 128 de blocuri (N=128) i fiecare bloc are 32 de fire (M=32). Ca urmare
variabila blockDim va avea valoarea 32 i vor fi 128*32=4096 de fire în grid. De exemplu, pentru firul 3
din blocul 5 variabila threadId va avea valoarea 5*32+3=163.
În general un grid este organizat ca tablou 2D de blocuri. Fiecare bloc este organizat ca tablou 3D de fire.
Organizarea exact a grid-ului este dat de configura ia de execu ie. Dup cum a fost precizat în capitolul
anterior, primul parametru al configura iei de execu ie specific dimensiunile grid-ului din punctul de
vedere al blocurilor de fire iar al doilea parametru specific dimensiunile blocului din punctul de vedere al
firelor. Fiecare astfel de parametru este de tipul dim3, care reprezint de fapt o structur C cu trei câmpuri
de tip întreg f semn: x, y i z. Deoarece grid-urile sunt tablouri 2D de blocuri, al treilea câmp al
parametrului care reprezint dimensiunea grid-ului este ignorat (trebuie setat la valoarea 1 pentru claritate).

Fig. 2.1 Organizarea firelor de execu ie CUDA.


Codul de mai jos poate fi folosit pentru a lansa kernel-ul a c rui organizare este prezentat în fig. 2.1.

dim3 dimGrid(128, 1, 1);


dim3 dimBlock(32, 1, 1);
KernelFunction<<<dimGrid, dimBlock>>>(. . .);

Primele dou instruc iuni ini ializeaz parametrii configura iei de execu ie. Deoarece grid-ul i blocurile
sunt tablouri unidimensionale, numai prima dimensiune a variabilelor predefinite dimBlock i dimGrid
este folosit . Celelalte dimensiuni sunt setate la valoarea 1. A treia instruc iune realizeaz lansarea efectiv a
kernel-ului. Parametrii configura iei de execu ie se specific între <<< i >>>. Se pot de asemenea utiliza i
valori scalare dac un grid-ul sau blocurile au o singur dimensiune, de exemplu:

KernelFunction<<<128, 32>>>(. . .);

Valorile variabilelor gridDim.x i gridDim.y pot fi între 1 i 65.535 i pot fi calculate înainte de
lansarea kernel-ului pe baza altor variabile din cadrul programului. Odat ce kernel-ul este lansat
dimensiunile nu mai pot fi îns schimbate. Toate firele dintr-un bloc au aceea i valoare a variabilei
blockIdx. Variabila blockIdx.x ia valori între 0 i gridDim.x-1 iar variabila blockIdx.y ia
valori între 0 i gridDim.y-1. Fig 2.2 ilustreaz un grid 2D mic care este lansat cu urm torul cod host:

dim3 dimGrid(2, 2, 1);


dim3 dimBlock(4, 2, 2);
KernelFunction<<<dimGrid, dimBlock>>>(. . .);

Grid-ul const din patru blocuri organizate într-un tablou 2x2. Fiecare bloc din fig. 2.2 este etichetat cu
valoarea (blockIdx.x, blockIdx.y).
În general blocurile con in structuri 3D de fire. Toate blocurile dintr-un grid au acelea i dimensiuni. Fiecare
variabil threadIdx const din trei componente: coordonata x threadIdx.x, coordonata y
threadIdx.y i coordonata z threadIdx.z. Num rul de fire ale fiec rei dimensiune a unui bloc este
specificat de cel de-al doilea parametru al configura iei de execu ie utilizate la lansarea kernel-ului.
Dimensiunea maxim a unui bloc este limitat la 512 fire de execu ie, care pot fi îns organizate în
nenum rate moduri în cadrul blocului atât timpul cât num rul lor total nu dep te 512: (512,1,1), (8,16,2)
i (16,16,2) sunt permise, dar (32,32,1) nu este permis deoarece num rul total de fire ar fi 1024.
Fig. 2.2. Exemplu de organizare multidimensional a unui grid CUDA.

Fig. 2.2 ilustreaz de asemenea un exemplu de organizare a firelor în cadrul unui bloc. În cazul de fa
fiecare bloc con ine 4x2x2 fire. Deoarece toate blocurile dintr-un grid au acelea i dimensiuni, este suficient
se prezinte un singur bloc. În cazul de fa este prezentat blocul (1,1) împreun cu cele 16 fire ale sale. În
total sunt 4 blocuri de câte 16 fire, deci în total 64 de fire în întreg grid-ul (s-au folosit numere mici pentru a
permite o ilustrare clar – în realitate se folosesc mii de fire de execu ie).

2.2. Folosirea variabilelor blockIdx i threadIdx


Din punctul de vedere al programatorului, func ionalitatea principal a variabilelor blockIdx i
threadIdx este de a furniza firelor de execu ie o posibilitate s se deosebeasc unele de altele atunci când
execut un anumit kernel. Cel mai des, variabilele se folosesc pentru a identifica zona de date cu care va
lucra fiecare fir de execu ie. Acest aspect a fost ilustrat în fig. 1.14 unde bucla, care realizeaz produsul
scalar, folose te valorile threadIdx.x i threadIdx.y pentru a identifica rândul matricei Md i
coloana matricei Nd utilizate la realizarea produsului. În continuare aceste variabile se vor folosi într-un
mod mai complicat.
O limitare important a codului simplu din fig. 1.14 este c acesta poate lucra cu matrice formate din maxim
16 elemente în fiecare dimensiune. Aceast limitare este dat de faptul c func ia kernel-ului nu folose te
variabila blockIdx i prin urmare se poate folosi un grid format dintr-un singur bloc. Chiar dac s-ar crea o
configura ie de execu ie cu mai multe blocuri, fire din diferite blocuri ar calcula acelea i loca ii din matricea
Pd, deoarece ar avea aceea i valoare a variabilei threadIdx. Astfel, pentru matrice p trate ar ap rea o
limitare de 16x16 fire deoarece o organizare de 32x32 necesit mai mult de 512 fire.
Pentru a putea utiliza matrice mai mari, trebuie folosite mai multe blocuri de fire de execu ie. Fig. 2.3
prezint ideea general a acestei abord ri. Practic Pd este împ it în por iuni p trate. Toate elementele
unei por iuni a matricei Pd sunt apoi calculate de un bloc de fire. Men inând aceste por iuni la dimensiuni
mici, num rul total de fire dintr-un bloc poate fi men inut sub 512. Pentru simplitate, în fig. 2.3,
threadIdx.x i threadIdx.y sunt prescurtate prin tx i ty. Similar, blockIdx.x i
bloxkIdx.y sunt prescurtate prin bx i by.
În continuare fiecare fir va calcula un singur element al matricei Pd. Diferen a este c de aceast dat este
nevoie s foloseasc valorile variabilei blockIdx pentru a identifica por iunea care trebuie calculat
înainte de folosi valorile variabilei threadIdx pentru a identifica elementele din cadrul por iunii. Astfel
fiecare fir va folosi atât threadIdx cât i blockIdx pentru a identifica elementul Pd pe care trebuie s -l
prelucreze. Acest aspect este ilustrat în fig. 2.3, unde valorile bx, by, tx i ty ale firelor care calculeaz
elementele matricei Pd sunt marcate atât în direc iile x i y.
Se presupune c blocurile sunt p trate i au valorile dimensiunilor date de variabila LATIME_PORTIUNE.
Fiecare dimensiune a matricei Pd este împ it în sec iuni având un num r de elemente egal cu
LATIME_PORTIUNE. Fiecare bloc gestioneaz o astfel de sec iune i astfel fiecare fir poate determina prin
(bx*LATIME_PORTIUNE + tx) indexul x al elementului din matricea Pd i prin
(by*LATIME_PORTIUNE + ty) indexul y. Astfel, firul (tx, ty) din blocul (bx, by) va utiliza
rândul (by*LATIME_PORTIUNE + ty) din matricea Md i coloana (bx*LATIME_PORTIUNE +
tx) din matricea Nd pentru calcula elementul din Pd aflat la coloana (bx*LATIME_PORTIUNE + tx)
i rândul (by*LATIME_PORTIUNE + ty).

Fig. 2.3. Înmul irea matricelor prin utilizarea mai multor blocuri.

Fig. 2.4 ilustreaz un mic exemplu de utilizare a mai multor blocuri pentru a calcula matricea Pd. Pentru
simplitate se folose te o valoare mic pentru variabila LATIME_PORTIUNE (2). Matricea Pd este împ it
în 4 por iuni, fiecare dimensiune a lui Pd fiind împ it în dou p i. Fiecare bloc trebuie s calculeze 4
elemente din matricea Pd. Acest lucru poate fi realizat prin crearea unor blocuri care sunt organizate în
tablouri de 2x2 fire, fiecare fir calculând un element Pd. În exemplu, firul (0,0) al blocului (0,0) calculeaz
elementul Pd0,0, iar firul (0,0) din blocul (1,0) calculeaz elementul Pd2,0. Elementul Pd calculat de firul
(0,0) din blocul (1,0) poate fi determinat astfel: Pd[bx* LATIME_PORTIUNE + tx][by*
LATIME_PORTIUNE + ty] = Pd[1*2 + 0][0*2 + 0]=Pd[2][0].
Odat ce au fost identifica i indicii elementului Pd care trebuie calculat de c tre un fir, s-a identificat i
rândul (y) din Md i coloana (x) din Nd de unde se preiau valorile de intrare. Dup cum este ilustrat în fig.
2.3., indicele rândului matricei Md utilizat de firul (tx, ty) din blocul (bx, by) este (by*
LATIME_PORTIUNE + ty). Indicele coloanei matricei Nd utilizate de acela i este (bx*
LATIME_PORTIUNE + tx). Se poate acum prezenta versiunea revizuit a kernel-ului din fig. 1.14, care
folose te mai multe blocuri pentru a calcula Pd.

Fig. 2.4. Exemplu simplificat de utilizare a mai multor blocuri pentru calculul matricei Pd.

Fig. 2.5 prezint ac iunile de înmul ire ale fiec rui bloc. Firele din blocul (0,0) realizeaz patru produse
scalare. Firul (0,0) îl determin pe Pd0,0 prin calculul produsului scalar al rândului 0 al matricei Md i al
coloanei 0 a matricei Nd. S ge ile de la Pd0,0, Pd1,0, Pd0,1 i Pd1,1 indic rândurile i coloanele utilizate
pentru calculul valorii rezultante.

Fig. 2.5. Ac iunile realizate de un bloc de fire la înmul irea matricelor.


Fig. 2.6 prezint varianta revizuit a kernel-ului pentru înmul irea matricelor. Fiecare fir folose te
variabilele blockIdx i threadIdx pentru a identifica indexul rândului (Row) i indexul coloanei (Col)
elementului Pd de care este responsabil acel fir. Apoi se realizeaz produsul scalar al rândului din Md i al
coloanei din Nd. În final stocheaz valoarea calculat în loca ia corespunz toare din memoria global . Acest
kernel poate gestiona o matrice de pân la 16x65.535 elemente în fiecare dimensiune. Dac trebuie
manipulate matrice chiar mai mari, matricea Pd poate fi împ it în submatrice de dimensiuni acceptabile
de c tre kernel.

global__ void InmultMatrKernel (float* Md, float* Nd, float* Pd, int
latime)
{
// Calculul indexului de rand al elementelor din matricele Pd si Md
int Row = blockIdx.y * blockDim.y + threadIdx.y;
// Calculul indexului de coloana al elementelor din matricele Pd si Nd
int Col = blockIdx.x * blockDim.x + threadIdx.x;
float Pval = 0;
// fiecare fir calculeaza un element din portiunea matricei
for (int k = 0; k < latime; ++k)
Pval += Md[Row*latime+k] * Nd[k*latime+Col];
Pd[Row*latime + Col] = Pval;
}

Fig. 2.6. Kernel revizuit utilizat pentru înmul irea matricelor, bazat pe mai multe blocuri de fire.

Fig. 2.7 prezint codul host revizuit utilizat pentru lansarea kernel-ului revizuit de înmul ire a matricelor. Se
remarc faptul c dimGrid ia valoarea latime/LATIME_PORTIUNE atât pentru dimensiunea x cât i
pentru dimensiunea y. Codul revizuit lanseaz kernel-ul cu mai multe blocuri.

//Setarea configuratiei de executie


dim3 dimGrid(latime/LATIME_PORTIUNE, latime/LATIME_PORTIUNE);
dim 3 dimBloc(LATIME_PORTIUNE, LATIME_PORTIUNE);

//lansarea firelor de executie pe dispozitivul CUDA


InmultMatrKernel<<<dimGrid,dimBloc>>>(Md,Nd,Pd,latime);

Fig. 2.7. Cod host revizuit care apeleaz kernel-ul revizuit.

2.3. Sincronizare i scalabilitate transparent


CUDA permite firelor din acela i bloc s i coordoneze activit ile prin utilizarea unei func ii care introduce
o barier de sincronizare: __syncthreads(). Când un kernel apeleaz __syncthreads(), firul care
realizeaz apelul se va bloca la acea loca ie pân când fiecare fir din bloc ajunge în acel punct. Aceasta
asigur faptul c toate firele dintr-un bloc au completat o faz din execu ia kernel-ului înainte de a trece la
urm toarea faz .
Sincronizarea la barier este o metod simpl i des utilizat în coordonarea activit ilor paralele. În CUDA,
o instruc iune __syncthreads() trebuie executat de toate firele din bloc. Când o instruc iune
__syncthreads() este plasat în interiorul unei condi ii if, trebuie asigurat faptul c fie toate firele
îndeplinesc condi ia, fie nici unul. În cazul unei instruc iuni if-then-else, dac fiecare cale are o
instruc iune __syncthreads(), atunci fie toate firele intr pe calea then, fie toate firele intr pe calea
else. Cele dou instruc iuni __syncthreads() reprezint bariere de sincronizare diferite i dac un fir
ar alege o cale iar alt fir cealalt cale atunci practic ambele fire s-ar a tepta reciproc la diferite bariere.
Capacitatea de sincronizare impune i constrângeri de execu ie asupra firelor unui bloc: firele trebuie s se
execute la momente apropiate de timp pentru a evita timpi de a teptare excesiv de lungi. Sistemul de
execu ie CUDA satisface aceast cerin prin modul de asociere dintre resurselor de execu ie i firele de
execu ie ale aceluia i bloc. Astfel când un fir al unui bloc este asociat unei resurse de execu ie, toate
celelalte fire din acela i bloc sunt asociate aceleia i resurse. Aceasta asigur proximitatea temporal a firelor
dintr-un bloc i conduce la evitarea timpilor de a teptare excesivi de lungi când se folosesc bariere de
sincronizare.
Astfel se ob ine un compromis major în design-ul barierelor de sincronizare CUDA. Deoarece nu se permite
firelor din diferite blocuri s se sincronizeze, sistemul de execu ie CUDA poate executa blocurile unui grid
în orice ordine deoarece nici un bloc nu trebuie s a tepte dup altul. Aceast flexibilitate permite
implement ri scalabile, dup cum este ilustrat în fig. 2.8. Într-o versiune rudimentar , cu pu ine resurse de
execu ie, pu ine blocuri de fire pot fi executate simultan (în partea stâng din fig. 2.8 se execut doar dou
blocuri simultan). În versiuni mai avansate, cu mai multe resurse de execu ie, se pot executa multe blocuri în
acela i timp (în partea dreapt din fig. 2.8 se execut doar patru blocuri simultan). Capacitatea de a executa
acela i cod de aplica ie la diferite viteze, permite realizarea unei game largi de implement ri,
corespunz toare cerin elor de cost, putere i performan ale unui anumit segment de pia . Un procesor
mobil de exemplu, poate executa o aplica ie lent dar i cu un consum de putere foarte mic. Pe de alt parte
un procesor de tip desktop poate executa aceea i aplica ie la viteze mai mari dar i cu un consum mai mare
de putere. În ambele cazuri se execut exact aceea i aplica ie i se ob ine exact acela i rezultat. Capacitatea
de a executa acela i cod pe dispozitive hardware cu numere diferite de resurse de execu ie se nume te
scalabilitate transparent . Aceast proprietate simplific sarcina programatorilor i cre te utilizabilitatea
aplica iilor.

Fig. 2.8. Scalabilitate transparent a programelor CUDA asigurat de lipsa sincroniz rii între blocurile de
fire.

2.4. Asocierea firelor de execu ie


Odat ce un kernel este lansat, sistemul de execu ie CUDA genereaz grid-ul corespunz tor de fire. Aceste
fire sunt asociate unor resurse de execu ie în func ie de blocul în care se afl . În genera ia curent , resursele
de execu ie sunt organizate în multiprocesoare: de exemplu GPU-ul NVIDIA GT200 are 30 de
multiprocesoare, dou dintre ele fiind ilustrate în fig. 2.9. Pân la 8 blocuri pot fi asociate fiec rui
multiprocesor, atâta timp cât exist suficiente resurse pentru execu ia simultan a 8 blocuri. Dac o anumit
resurs este insuficient pentru a executa simultan 8 blocuri, atunci sistemul CUDA va reduce automat
num rul de blocuri simultane de pe un multiprocesor pân când sunt respectate toate limitele impuse de
resurse. Pe procesorul GT200, care are 30 de multiprocesoare, pot fi executate simultan pân la 240 de
blocuri. Multe grid-uri con in îns mai mult 240 de blocuri Sistemul de execu ie ine eviden a blocurilor
care trebuie executate i asociaz noi blocuri multiprocesoarelor pe m sur ce se încheie execu ia blocurilor
asociate anterior.
Fig. 2.9 prezint un exemplu în care trei blocuri de fire sunt asociate fiec rui multiprocesor. Una din
limit rile impuse unui multiprocesor este dat de num rul de fire care pot fi gestionate simultan (este nevoie
de resurse hardware pentru a stoca valorile threadIdx i blockIdx i pentru a gestiona execu ia
firelor). Pentru GT200, pân la 1024 de fire pot fi asociate simultan unui multiprocesor. Ar putea fi astfel
folosite 4 blocuri de câte 256 de fire, 8 de câte 128, etc. Deoarece GT200 are 30 de multiprocesoare, pân la
30720 de fire pot fi asociate simultan multiprocesoarelor.

Fig. 2.9. Atribuirea blocurilor de fire c tre multiprocesoare.

2.5. Planificarea firelor i toleran a fa de laten


Planificarea firelor este strict un concept de implementare i trebuie discutat în contextul unor implement ri
hardware specifice. Pentru GT200, odat ce un bloc este asociat unui multiprocesor, acesta este apoi
împ it în grupuri de câte 32 de fire numite warp-uri. Dimensiunea warp-urilor depinde de hardware. Warp-
urile nu fac practic parte din specifica ia CUDA, dar cuno tin ele referitoare la warp-uri pot fi utile în
în elegerea performan elor aplica iilor CUDA i apoi a abord rilor de optimizare pentru diverse genera ii de
dispozitive CUDA. Un warp este unitatea planific rii firelor la multiprocesoare: fig. 2.10 ilustreaz
împ irea blocurilor în warp-uri pentru un dispozitiv GT200. Fiecare warp con ine 32 de fire cu valori
consecutive ale variabilei threadIdx: firele 0 pân la 31 formeaz primul warp, firele 32-63 formeaz al
doilea warp, etc. În acest exemplu, toate cele trei blocuri sunt asociate aceluia i multiprocesor, iar fiecare
bloc este împ it apoi în warp-uri pentru a preg ti planificarea firelor.
Fig. 2.10. Gruparea firelor în warp-uri.

Se poate calcula num rul de warp-uri care se afl simultan pe un multiprocesor folosind dimensiunea unui
bloc i num rul de blocuri asociate fiec rui multiprocesor. Dac de exemplu fiecare bloc are 256 de fire,
adic 8 warp-uri, i trei blocuri sunt asociate simultan unui multiprocesor, atunci în total exist 24 de warp-
uri pe un multiprocesor la un moment dat. Num rul maxim de warp-uri pentru multiprocesoarele
dispozitivului GT200 este de 32 deoarece sunt permise maxim 1024 pe un multiprocesor.
Întrebarea este de ce este nevoie de atâtea warp-uri, dac nu sunt decât 8 unit i de procesare într-un
multiprocesor? R spunsul este c acesta modul în care procesoarele CUDA execut eficient opera iile cu
laten mare, precum acces rile memoriei globale. Când o instruc iune executat de firele unui warp trebuie
a tepte rezultatul unei opera ii ini iate anterior, i care are o laten mare, atunci warp-ul respectiv nu este
selectat pentru execu ie. Un alt warp, care nu trebuie s a tepte rezultatul unei opera ii este atunci selectat
pentru execu ie. Dac mai mult de un warp este preg tit de execu ie, un mecanism de prioritate este folosit
pentru a selecta un warp în vederea execu iei sale. Acest mecanism de contracarare a laten ei opera iilor prin
executarea altor fire se nume te mascare a laten ei.
Planificarea warp-urilor este utilizat i pentru tolerarea altor tipuri de opera ii cu laten mare, precum
opera ii în virgul mobil sau opera ii de ramificare. Dac exist suficiente warp-uri, este probabil ca
hardware-ul s g seasc un warp pe care s -l poat executa la un moment dat i s se foloseasc în acest fel
la maxim hardware-ul în ciuda prezen ei opera iilor de laten mare. Selec ia warp-urilor preg tite de
execu ie nu introduce timpi de pauz de-a lungul execu iei. Prin planificarea warp-urilor, timpii de a teptare
lungi ai instruc iunilor warp-urilor sunt ascun i prin executarea instruc iunilor altor warp-uri. Aceast
capacitate de tolerare a opera iilor de laten mare este unul din motivele principale pentru care GPU-urile
nu aloc nici pe de parte la fel de multe resurse pentru memorii cache i mecanisme de predic ie a
comportamentului de ramificare precum procesoarele clasice CPU. Prin urmare, GPU-urile dedic o mai
mare parte a chip-ului resurselor necesare pentru efectuarea calculelor în virgul mobil .
Se analizeaz în continuare un exemplu simplu. Care dintre urm toarele dimensiuni de blocuri, 8x8, 16x16
sau 32x32, trebuie folosite pentru dispozitivul GT200. Dac s-ar alege op iunea 8x8, fiecare bloc ar avea
doar 64 de fire i ar fi nevoie de 1024/64=12 blocuri pentru a ocupa pe deplin un multiprocesor (s-ar folosi
doar 512 fire pentru fiecare multiprocesor datorit limitei de 8 blocuri). E probabil astfel ca resursele
multiprocesorului s nu fie utilizate la maxim, deoarece mai pu ine warp-uri sunt disponibile pentru mascare
opera iilor cu laten mare. Cu 16x16 fire pe bloc se ob in 256 de fire pe bloc, ceea ce înseamn 1024/256=4
blocuri, adic sub limita de 8 blocuri. Aceasta este o configura ie bun deoarece s-ar ob ine num rul maxim
de warp-uri. Trebuie îns re inut faptul c acesta este un exemplu simplificat i sunt i alte aspecte care
trebuie luate în considerare atunci când se determin num rul de blocuri care pot fi asociate simultan unui
multiprocesor.

S-ar putea să vă placă și