3. fejezet: Párhuzamos programozási modellek GPU-tervezésben
A Grafikus Processzor Egységek (GPU-k) fejlődtek a rögzített funkciójú grafikai gyorsítóktól a nagy párhuzamosságú, programozható számítási motorokig, melyek képesek széles körű alkalmazások gyorsítására. A GPU-k hatalmas párhuzamosságának hatékony kihasználása érdekében több párhuzamos programozási modell és API-t fejlesztettek ki, mint például a NVIDIA CUDA, az OpenCL és a DirectCompute. Ezek a programozási modellek olyan absztrakciót biztosítanak, amely lehetővé teszi a programozók számára, hogy kifejezzék alkalmazásaik párhuzamosságát, miközben elrejtik a GPU hardver alacsony szintű részleteit.
Ebben a fejezetben megvizsgáljuk a GPU-k párhuzamos programozási modelljeinek kulcsfontosságú fogalmait és elveit, összpontosítva a SIMT (Single Instruction, Multiple Thread) végrehajtási modellre, a CUDA programozási modellre és API-ra, valamint az OpenCL keretrendszerre. Emellett tárgyaljuk azokat a technikákat is, amelyek segítségével algoritmusokat képezhetünk le GPU-architektúrákra a nagy teljesítmény és hatékonyság elérése érdekében.
A SIMT (Single Instruction, Multiple Thread) végrehajtási modell
A SIMT végrehajtási modell az a alapvető paradigma, amelyet a modern GPU-k használnak a hatalmas párhuzamosság elérésére. A SIMT modellben számos szál ugyanazt a programot (kernel-nek nevezve) hajtja végre párhuzamosan, de minden egyes szálnak saját program számlálója van, és a szál azonosítója és a kezelt adatok alapján különböző végrehajtási útvonalakat követhet.
Kernel-ek és a szál-hierarchia
Egy GPU kernel egy olyan függvény, amelyet nagy számú szál párhuzamosan hajt végre. Egy kernel indításakor a programozó megadja a létrehozandó szálak számát, és hogy azok hogyan szerveződnek hierarchiába: gridekbe, blokkok (vagy kooperatív szál-tömbök - CTAs) és egyedi szálak.
-
Egy grid reprezentálja a teljes problémateret, és egy vagy több blokkból áll.
-
Egy blokk olyan szálak csoportja, amelyek együttműködhetnek és szinkronizálhatnak egymással megosztott memória és korlátok segítségével. A blokkban lévő szálak ugyanazon a GPU maggal (streaming multiprocessor) lesznek végrehajtva.Itt a magyar fordítás:
-
Minden szál rendelkezik egyedi azonosítóval a blokkján és rácson belül, ami felhasználható a memóriacímek kiszámításához és vezérlési folyamatok meghozatalához.
Ez a hierarhikus szervezés lehetővé teszi a programozók számára, hogy kifejezzék mind az adatpárhuzamosságot (amikor ugyanazt a műveletet több adatelemre alkalmazzák), mind a feladatpárhuzamosságot (amikor különböző feladatok hajtódnak végre párhuzamosan).
A 3.1. ábra szemlélteti a szálhierarchiát a SIMT végrehajtási modellben.
Rács
________________
/ / / / /
/ / / / /
/ / / / /
/ / / / /
/__/__/__/__/__/
| | | |
| | Blokk |
| | | |
Szál Szál ...
3.1. ábra: Szálhierarchia a SIMT végrehajtási modellben.
SIMT végrehajtás
A SIMT végrehajtási modellben minden szál ugyanazt az utasítást hajtja végre, de különböző adatokon működik. Azonban, ellentétben a SIMD-vel (Single Instruction, Multiple Data), ahol minden feldolgozó egység lépést tart, a SIMT lehetővé teszi, hogy a szálak független végrehajtási útvonalakkal rendelkezzenek és elágazásoknál eltérjenek.
Amikor egy warp (32 szál NVIDIA GPU-kban vagy 64 szál AMD GPU-kban) elágazási utasítással találkozik, a GPU hardver kiértékeli az elágazási feltételt minden szálban. Ha minden szál ugyanazt az utat választja (konvergált), a warp normál módon folytatja a végrehajtást. Azonban, ha néhány szál különböző utakat választ (divergált), a warp két vagy több alwarpra osztódik, mindegyik a saját útját követve. A GPU hardver szekvenciálisan hajtja végre a divergens utakat, elrejtve a nem aktív szálakat minden alwarpban. Amikor minden út befejeződik, az alwarpok újra konvergálnak és lépést tartva folytatják a végrehajtást.
A 3.2. ábra szemlélteti a SIMT végrehajtást divergens vezérlési folyamattal.
Warp
________________
/ / / / /
/ / / / /
/ / / / /
| | |
| Elágazás |
| | |
/ \ / \ / \
/ X \ \
/ / \ \ \
/ \ \
/ \ \
/ \ \
/ \ \
\
```Újrasűrűsödés
3.2. ábra: SIMT végrehajtás divergens vezérlési folyamattal.
Ez a divergencia-kezelési mechanizmus lehetővé teszi, hogy a SIMT rugalmasabb vezérlési folyamatot támogasson, mint a SIMD, de ez a divergencia előfordulása esetén csökkenti a SIMD hatékonyságát. A programozóknak arra kell törekedniük, hogy minimalizálják a divergenciát egy warp-on belül az optimális teljesítmény elérése érdekében.
Memória hierarchia
A GPU-k komplex memória-hierarchiával rendelkeznek, hogy támogassák a párhuzamos terhelések nagy sávszélességű és alacsony késleltetésű követelményeit. A memória hierarchia általában a következőket tartalmazza:
- Globális memória: A legnagyobb, de leglassúbb memóriatér, amelyhez minden szál hozzáférhet egy kernelen belül. A globális memória általában nagy sávszélességű GDDR vagy HBM memóriával valósul meg.
- Megosztott memória: Egy gyors, chipre integrált memóriatér, amelyet az összes szál megoszthat egy blokkban. A megosztott memóriát az inter-szál kommunikáció és az adatmegosztás céljára használják egy blokon belül.
- Állandó memória: Csak olvasható memóriatér, amely az összes szál számára közvetíti a csak olvasható adatokat.
- Textúra memória: Egy csak olvasható memóriatér, amely a térbeli lokalitásra van optimalizálva, és textúra gyorsítótárakon keresztül érhető el. A textúra memóriát általában grafikai munkafolyamatokban használják.
- Helyi memória: Egy magánjellegű memóriatér minden egyes szál számára, amely a regiszter túlcsordulás és a nagy adatszerkezetek kezelésére szolgál. A helyi memória általában a globális memóriához van leképezve.
A memória-hierarchia hatékony kihasználása kulcsfontosságú a GPU-kon való magas teljesítmény elérése érdekében. A programozóknak arra kell törekedniük, hogy maximalizálják a megosztott memória használatát, és minimalizálják a globális memóriához való hozzáférést, hogy csökkentsék a memória-késleltetést és a sávszélesség-problémákat.
A 3.3. ábra a GPU memória-hierarchiáját mutatja be.
____________
| |
| Global |
| Memory |
____________
|
____________
| |
| Constant |
| Memory |
____________
|
____________
| |
| Texture |
| Memory |
____________
|
|
____________
```Íme a magyar fordítás a megadott markdown fájlhoz. A kódhoz tartozó megjegyzéseket fordítottam le, a kódot nem módosítottam.
| |
| Megosztott |
| Memória |
____________
|
____________
| |
| Helyi |
| Memória |
____________
3.3. ábra: GPU memória hierarchia.
CUDA Programozási Modell és API-k
A CUDA (Compute Unified Device Architecture) egy párhuzamos számítástechnikai platform és programozási modell, amelyet az NVIDIA fejlesztett ki általános célú számítások GPU-kon történő végrehajtására. A CUDA a standard programozási nyelvek, például a C, C++ és a Fortran, kiterjesztéseit biztosítja, amelyek lehetővé teszik a programozók számára a párhuzamosság kifejezését és az NVIDIA GPU-k számítási teljesítményének kihasználását.
CUDA Programozási Modell
A CUDA programozási modell a kernel-ek koncepciójára épül, amelyek olyan függvények, amelyeket párhuzamosan hajt végre a GPU-n lévő nagy számú szál. A programozó meghatározza az indítandó szálak számát és azok szerveződését blokkokba.
A CUDA több kulcsfontosságú absztrakciót vezet be a párhuzamos programozás megkönnyítése érdekében:
- Szál: A végrehajtás alapegysége a CUDA-ban. Minden szálnak saját programszámlálója, regiszterei és helyi memóriája van.
- Blokk: Olyan szálak csoportja, amelyek egymással együttműködhetnek és szinkronizálhatnak. A blokkban lévő szálakat ugyanazon streaming multiprocesszor hajtja végre, és megosztott memórián keresztül kommunikálhatnak.
- Háló: Azonos kernelt végrehajtó szálblokkok gyűjteménye. A háló jeleníti meg a teljes problémateret, és lehet egy-, két- vagy háromdimenziós.
A CUDA továbbá beépített változókat (pl. threadIdx, blockIdx, blockDim, gridDim) biztosít, amelyek lehetővé teszik a szálak számára, hogy azonosítsák magukat és a memóriacímeket a szál-hierarchiában elfoglalt pozíciójuk alapján kiszámítsák.
A 3.4. ábra a CUDA programozási modellt szemlélteti.
Háló
________________
/ / / / /
/ / / / /
/ / / / /
/ / / / /
/__/__/__/__/__/
| | | |
| | Blokk |
| | | |
Szál Szál ...
3.4. ábra: CUDA programozási modell.
CUDA Memória HierarchiaItt a markdown fájl magyar fordítása. A kódnál nem fordítottam le a kódot, csak a megjegyzéseket.
CUDA feltárja a GPU memóriahierarchiáját a programozó előtt, lehetővé téve az adatelhelyezés és mozgatás explicit vezérlését. A CUDA fő memóriaterületei:
- Globális memória: Minden kernel szálból elérhető és a kernelhívások között is megmarad. A globális memóriának a legnagyobb a késleltetése, és általában nagy adatstruktúrák tárolására használják.
- Megosztott memória: Gyors, a chip-en belüli memória, amely az összes szál által megosztott a blokkban. A megosztott memóriát az egymás közötti kommunikációra és adatmegosztásra használják a blokkon belül.
- Állandó memória: Csak olvasható memóriatér, amely csak olvasható adatok szétosztására szolgál az összes szál között. Az állandó memória gyorsítótárazott, és alacsony késleltetésű hozzáférést biztosít.
- Textúra memória: Csak olvasható memóriatér, amely a térbeli lokalitásra van optimalizálva, és a textúra gyorsítótárakon keresztül érhető el. A textúra memóriát általában grafikai feladatokban használják.
- Helyi memória: Egy privát memóriatér minden egyes szál számára, amelyet regiszter kiömlesztésre és nagy adatstruktúrák tárolására használnak. A helyi memória általában a globális memóriára van leképezve.
A programozók a CUDA futtatókörnyezeti API-k, mint a cudaMalloc, cudaMemcpy és cudaFree segítségével foglalhatnak és vihetnek át adatot a gazdagép (CPU) és az eszköz (GPU) memóriája között.
A 3.5. ábra szemlélteti a CUDA memóriahierarchiáját.
____________
| |
| Global |
| Memory |
____________
|
____________
| |
| Constant |
| Memory |
____________
|
____________
| |
| Texture |
| Memory |
____________
|
|
____________
| |
| Shared |
| Memory |
____________
|
____________
| |
| Local |
| Memory |
____________
3.5. ábra: A CUDA memóriahierarchiája.
CUDA Szinkronizáció és Koordináció
A CUDA szinkronizációs és koordinációs primitíveket biztosít a szálak közötti együttműködés és kommunikáció lehetővé tételére:
- Gát-szinkronizáció: A __syncthreads()Here is the Hungarian translation of the provided markdown file:
s() függvény egy akadály, amely biztosítja, hogy a blokk összes szála elérje ugyanazt a pontot, mielőtt továbblépne.
- Atomi műveletek: A CUDA támogatja az atomi műveleteket (pl. atomicAdd, atomicExch), amelyek lehetővé teszik a szálak számára, hogy olvas-módosít-ír műveleteket végezzenek a megosztott vagy globális memórián anélkül, hogy más szálak beavatkoznának.
- Warp-szintű primitívek: A CUDA warp-szintű belsőépítményeket (pl. __shfl, __ballot) biztosít, amelyek hatékony kommunikációt és szinkronizációt tesznek lehetővé egy warpon belül.
A szinkronizációs és koordinációs primitívek megfelelő használata alapvető fontosságú a helyes és hatékony párhuzamos programok írásához a CUDA-ban.
A 3.1 példa egy egyszerű CUDA-kernelt mutat be, amely vektor-összeadást végez.
__global__ void vectorAdd(int *a, int *b, int *c, int n) {
// Az i index kiszámítása a blokk és a szál indexe alapján
int i = blockIdx.x * blockDim.x + threadIdx.x;
if (i < n) {
// A c vektor i-edik elemének kiszámítása az a és b vektorok i-edik elemeinek összeadásával
c[i] = a[i] + b[i];
}
}
int main() {
int *a, *b, *c;
int n = 1024;
// Memória foglalása a gazdagépen
a = (int*)malloc(n * sizeof(int));
b = (int*)malloc(n * sizeof(int));
c = (int*)malloc(n * sizeof(int));
// A bemeneti vektorok inicializálása
for (int i = 0; i < n; i++) {
a[i] = i;
b[i] = i * 2;
}
// Memória foglalása az eszközön
int *d_a, *d_b, *d_c;
cudaMalloc(&d_a, n * sizeof(int));
cudaMalloc(&d_b, n * sizeof(int));
cudaMalloc(&d_c, n * sizeof(int));
// A bemeneti vektorok másolása a gazdagépről az eszközre
cudaMemcpy(d_a, a, n * sizeof(int), cudaMemcpyHostToDevice);
cudaMemcpy(d_b, b, n * sizeof(int), cudaMemcpyHostToDevice);
// A kernel elindítása
int blockSize = 256;
int numBlocks = (n + blockSize - 1) / blockSize;
vectorAdd<<<numBlocks,blockSize>>>(d_a, d_b, d_c, n);
// Az eredmény vektor másolása az eszközről a gazdagépre
cudaMemcpy(c, d_c, n * sizeof(int), cudaMemcpyDeviceToHost);
// Az eszköz memóriájának felszabadítása
cudaFree(d_a);
cudaFree(d_b);
cudaFree(d_c);
// A gazdagép memóriájának felszabadítása
free(a);
free(b);
free(c);
returAlább található a magyar fordítás a megadott markdown fájlhoz. A kódban csak a kommenteket fordítottam le, a kód maga változatlan maradt.
```c
// Inicializálja a vektorok elemeit
for (int i = 0; i < N; i++) {
a[i] = 1.0f;
b[i] = 2.0f;
c[i] = 0.0f;
}
Ez a CUDA kód elindítja a vectorAdd
kernel függvényt numBlocks
blokkokban és blockSize
szálon per blokk. A kernel végrehajtja a bemeneti a
és b
vektorok elemenként való összeadását, és az eredményt a c
vektorba tárolja. A <<<...>>>
szintaxis használatos a rács és blokk méretek megadására a kernel indításakor.
CUDA Streamek és Események
A CUDA streamek és események egy mechanizmust biztosítanak a kernelekben és memória műveletekben való egyidejű végrehajtásra és szinkronizációra:
- Streamek: Műveletek (kernel indítás, memória másolás) sora, amelyek sorrendben hajtódnak végre. Különböző streamek párhuzamosan futhatnak, lehetővé téve a számítás és memória átvitel átfedését.
- Események: Jelzők, amelyek beilleszthetők egy streambe, hogy rögzítsék a művelet befejezését. Az események szinkronizálásra és időzítésre használhatók.
A streamek és események lehetővé teszik a programozók számára, hogy optimalizálják a CUDA alkalmazások teljesítményét azáltal, hogy átfedést biztosítanak a számítás és memória átvitel között, és kihasználják a GPU hardver teljes képességeit.
A 3.2 példa bemutatja a CUDA streamek használatát a kernel végrehajtás és memória átvitel átfedésére.
// Két stream létrehozása
cudaStream_t stream1, stream2;
cudaStreamCreate(&stream1);
cudaStreamCreate(&stream2);
// Bemeneti adatok aszinkron másolása az eszközre
cudaMemcpyAsync(d_a, a, size, cudaMemcpyHostToDevice, stream1);
cudaMemcpyAsync(d_b, b, size, cudaMemcpyHostToDevice, stream2);
// Kernelindítás különböző streamekben
kernelA<<<blocks, threads, 0, stream1>>>(d_a);
kernelB<<<blocks, threads, 0, stream2>>>(d_b);
// Eredmények aszinkron másolása vissza a gazdagépre
cudaMemcpyAsync(a, d_a, size, cudaMemcpyDeviceToHost, stream1);
cudaMemcpyAsync(b, d_b, size, cudaMemcpyDeviceToHost, stream2);
// Streamek szinkronizálása
cudaStreamSynchronize(stream1);
cudaStreamSynchronize(stream2);
Ebben a példában két CUDA stream jön létre. A bemeneti adatok aszinkron módon kerülnek másolásra az eszközre, mindegyik stream használatával. Ezt követően a kerneleket indítjuk a különböAlább található a markdown fájl magyarra fordított változata. A kódhoz tartozó megjegyzéseket fordítottam le, a kódot változatlan formában hagytam.
OpenCL keret
Az OpenCL (Open Computing Language) egy nyílt, szabadalom- és díjmentes szabvány a heterogén platformokon (beleértve a CPU-kat, GPU-kat, FPGA-kat és más gyorsítókat) történő párhuzamos programozáshoz. Az OpenCL egységes programozási modellt és API-készletet biztosít, amely lehetővé teszi a fejlesztők számára, hogy hordozható és hatékony párhuzamos kódot írjanak.
OpenCL programozási modell
Az OpenCL programozási modell hasonlít a CUDA-hoz, néhány kulcsfontosságú terminológiai és absztrakciós különbséggel:
- Kernel: Egy olyan függvény, amelyet párhuzamosan futtat egy nagy számú munka-elem (szál) egy OpenCL eszközön.
- Munka-elem: Az OpenCL végrehajtásának alapvető egysége, ami a CUDA-ban szálnak felel meg.
- Munka-csoport: A munka-elemek egy gyűjteménye, amelyek szinkronizálni és adatokat megosztani tudnak a helyi memórián keresztül. A munka-csoportok a CUDA-ban a szálblokkoknak felelnek meg.
- NDRange: Meghatározza a kernel végrehajtásának index-terét és a munka-elemek szervezését. Lehet egy-, két- vagy háromdimenziós.
Az OpenCL egy hierarchikus memóriamodellt is meghatároz, hasonlóan a CUDA-hoz:
- Globális memória: Minden munka-elem által minden munka-csoportban elérhető, a CUDA-beli globális memóriának felel meg.
- Helyi memória: Minden munka-elem által az adott munka-csoportban megosztható, a CUDA-beli megosztott memóriának felel meg.
- Privát memória: Egyetlen munka-elemhez privát, a CUDA-beli nyilvántartásoknak felel meg.
- Állandó memória: Csak olvasható memória, amely minden munka-elem által elérhető.
Az OpenCL kerneleket futási időben fordítja le az OpenCL futtatókörnyezet. A gazdaprogram lekérdezheti az elérhető OpenCL eszközöket, kiválaszthatja a megfelelő eszközt, létrehozhatja a kontextust, és lefordíthatja a kernelt arra az adott eszközre. Ez lehetővé teszi az OpenCL alkalmazások nagy fokú hordozhatóságát különböző hardverplatformok között.
A 3.3. példa egy OpenCL kernelt mutat be, amely vektorösszeadást végez, hasonlóan a 3.1. példa CUDA-s példájához.
__kernel void vectorAdd(__global const int *a, __global int *b, __global int *c, int n) {
int i = get_global_id(0);
if (i < n)
c[i] = a[i] + b[i];
}
// A kernel egy vektorösszeadást végző függvény // Minden munka-elem a saját indexén lévő elemeket adja össze // A munka-elemek indexe a get_global_id(0) függvénnyel kérhető le // A feltétel biztosítja, hogy csak azok a munka-elemek végezzék el a műveletet, amelyek indexe a vektor méretén belül vanItt a magyar fordítás a megadott markdown fájlhoz. A kódnál csak a kommenteket fordítottam le, a kódot nem módosítottam:
void __kernel bal const int *b, __global int *c, int n) {
int i = get_global_id(0);
if (i < n) {
c[i] = a[i] + b[i];
}
}
A __kernel
kulcsszó egy OpenCL kernel függvényt határoz meg. A __global
kulcsszó azt jelzi, hogy a mutató a globális memóriára mutat. A get_global_id
függvény visszaadja a jelenlegi munkaelem globális indexét, amit a bemeneti és kimeneti vektorok memóriacímének kiszámításához használunk.
Algoritmusok leképezése GPU architektúrákra
Az algoritmusok hatékony leképezése a GPU architektúrára kulcsfontosságú a magas teljesítmény eléréséhez. A fő szempontok a következők:
-
Elegendő párhuzamosság kihasználása: Az algoritmust sok apró szálra kell bontani, amelyek párhuzamosan futhatnak a GPU-k párhuzamos feldolgozási képességeinek teljes kihasználása érdekében.
-
Ágak szétválásának minimalizálása: A warp/wavefront-on belüli divergens vezérlési folyam serialization-hez és csökkent SIMD-hatékonysághoz vezethet. Az algoritmusokat úgy kell kialakítani, hogy az ágak szétválása a lehető legkisebb legyen.
-
A memória-hierarchia kihasználása: A globális memória elérése költséges. Az algoritmusoknak a megosztott memória és a regiszterek használatát kell maximalizálniuk a globális memória-hozzáférések csökkentése érdekében. Az adatokat is úgy kell elrendezni a memóriában, hogy lehetővé tegyék a koaleszált memória-hozzáféréseket.
-
Számítás és memória-hozzáférések egyensúlya: Az algoritmusoknak magas arányt kell biztosítaniuk a számítási műveletek és a memória-műveletek között, hogy hatékonyan elrejtsék a memória-késleltetést, és magas számítási átviteli sebességet érjenek el.
-
Gazdagép-eszköz adatátvitelek minimalizálása: A gazda- és eszköz-memória közötti adatátvitel lassú. Az algoritmusoknak a lehető legtöbb számítást a GPU-n kell elvégezniük, hogy minimalizálják ezeket az adatátviteleket.
A GPU kernelekben gyakran használt párhuzamos algoritmus-tervezési minták a következők:
-
Leképezés (Map): Minden szál ugyanazt a műveletet hajtja végre egy különböző adatelemen, lehetővé téve a nagy adatkészletek egyszerű párhuzamos feldolgozását.
-
Csökkentés (Reduce): A párhuzamos csökkentés egy hatékony módszer arra, hogy egyetlen értéket (pl. összeg, maximum) számítsunk ki egy nagy bemeneti adatkészletből.Szálak helyi csökkentéseket végeznek, amelyeket aztán összekapcsolnak a végső eredmény előállításához.
-
Szkennelés: Más néven előtag összeg, a szkennelést használják egy tömb elemeinek futó összegének kiszámítására. A hatékony párhuzamos szkennelési algoritmusok kulcsfontosságú építőkövei sok GPU-gyorsított alkalmazásnak.
-
Sablonozás: Minden szál egy érték kiszámítását végzi a szomszédos adatelemek alapján. A sablonszámítások gyakoriak tudományos szimulációkban és képfeldolgozási alkalmazásokban.
-
Gyűjtés/Szórás: A szálak tetszőleges helyekről olvasnak (gyűjtenek) vagy írnak (szórnak) a globális memóriába. A hatékonyság érdekében gondos adatelrendezésre és hozzáférési mintákra van szükség.
Következtetés
A CUDA és az OpenCL GPU-programozási modellek megjelenítik a modern GPU-k párhuzamos feldolgozási képességeit a fejlesztők számára, lehetővé téve, hogy széles körű alkalmazásokat gyorsítsanak fel. Ezek a programozási modellek absztrakciókat biztosítanak, amelyek lehetővé teszik, hogy a finoman bontott párhuzamos munkaterheléseket hatékonyan lehessen leképezni a GPU hardverre.
A GPU-programozási modellek végrehajtási modelljének, memóriahierarchiájának és szinkronizációs primitívjeinek megismerése elengedhetetlen a nagy teljesítményű GPU-kód írásához. A fejlesztőknek gondosan figyelembe kell venniük tényezőket, mint például a szálak szerveződését, az ági divergenciát, a memória-hozzáférési mintákat és az algoritmus tervezését, hogy teljes mértékben kihasználhassák a GPU-k számítási teljesítményét.
Ahogy a GPU-architektúrák folyamatosan fejlődnek, a programozási modelleknek és eszközöknek is előre kell lépniük, hogy lehetővé tegyék a fejlesztők számára, hogy hatékonyan használják ki az új hardveres funkciókat és képességeket. A programozási nyelv tervezésével, a fordítóprogramok optimalizálásával és az automatikus hangolással kapcsolatos kutatások folytatása kulcsfontosságú lesz a programozói termelékenység és a teljesítményhordozhatóság javítása szempontjából a heterogén számítástechnika korszakában.