2. Az OpenCL API eszközei
7.11. példa - memory.c:56-63
user@home> ./memory 5 6 7 8 9
A példaprogramban egy egyszerű, egy eszközből álló környezetben létrehozunk egy memóriaobjektumot, feltöltünk oda egy egészekből álló számsort, majd a feltöltött tömb második felét az output tömbbe olvassuk.
Mind az olvasási, mind az írási műveletet blokkoló műveletként hajtjuk végre, ami azt jelenti, hogy a program futása az adott ponton blokkolódik a művelet befejeződéséig. Ha nem blokkoló módon valósítjuk meg az írást és olvasást (ahogy azt korábban, más technológiáknál már láthattuk, a nem-blokkoló műveletek általában hatékonyabbak), akkor a függvények által visszaadott esemény objektumokat használhatjuk fel szinkronizációra. A példaprogramot az alábbi módon módosítva azonos működést kapunk, azonban az esemény objektumok gondoskodnak róla, hogy a megfelelő műveletek biztosan egymás után történjenek.
7.11. példa - memory.c:56-63
err= clEnqueueWriteBuffer(queue, memobj, 0, 0, sizeof(int)*ARRAY_SIZE, input, NULL, NULL, &event);
ERROR(err, "clEnqueueWriteBuffer");
err= clEnqueueReadBuffer(queue, memobj, 0, sizeof(int)*(ARRAY_SIZE/2), sizeof(int)*(ARRAY_SIZE/2), output, 1, &event, NULL);
ERROR(err, "clEnqueueReadBuffer");
err= clFinish(queue);
A memóriakezelő függvényeket most nem-blokkoló módon használjuk, ennek megfelelően harmadik paraméterük logikai hamis, azaz 0. Arról, hogy a memóriaterület írása biztosan megelőzze az olvasást, a clEnqueueReadBuffer függvénynek átadott esemény objektummal gondoskodunk, azt pedig, hogy a kimenetre írás biztosan az olvasás után történjen, a clFinish függvény explicit hívásával biztosítjuk.
Program objektumok. Áttekintettük eddig az OpenCL eszközrendszer inicializálásának lépéseit és a legegyszerűbb memóriakezelő függvények használatát. A párhuzamos végrehajtás felé vezető következő lépés a párhuzamosan végrehajtható kernelek létrehozása. A kernel programok írására használható OpenCL C nyelvvel a következő szakaszban foglalkozunk részletesebben. Jelen szakasz hátralévő részében az OpenCL C nyelvű programok fordításának és futtatásának lépéseit vizsgáljuk.
Az OpenCL C nyelvű forráskódokat és az OpenCL eszközre lefordított gépi kódot un. program objektumok formájában kezeljük. Program objektumot OpenCL C nyelvű forráskódból a clCreateProgramWithSource függvénnyel hozhatunk létre.
Specifikáció:
cl_program clCreateProgramWithSource(
cl_context context,
cl_uint count,
const char** strings,
const size_t* lengths,
cl_int* errcode_ret);
Paraméterek: context - A párhuzamos végrehajtás alapjául szolgáló környezet.
count - A strings tömb mérete.
strings - Az OpenCL C nyelvű forráskódot tartalmazó karaktertömbök mutatói.
lengths - A strings karaktertömbök hosszai.
errcode_ret - Hiba esetén a hibakód ezen címre kerül beállításra.
Visszatérési érték: Sikeres végrehajtás esetén egy cl_program objektum, ellenkező esetben beállításra kerül a hibakód.
Az OpenCL C nyelvű forráskód egy lépésben történő fordítására és linkelésére használhatjuk a clBuildProgram függvényt. Megjegyezzük, hogy a fordítás és linkelés két lépésben, külön függvények segítségével is elvégezhető (clCompileProgram, clLinkProgram), használatukra azonban csak összetett esetekben van szükség, ezért azt nem részletezzük.
7.5. táblázat - A legfontosabb OpenCL C fordítási és linkelési kapcsolók.
Kapcsoló Leírás
-D name Az előfeldolgozó definiálja a name konstanst 1 értékkel, használata teljesen analóg a GCC megfelelő kapcsolóinak használatával.
-D name=definition Az előfeldolgozó definiálja a name makrót definition értékkel.
-I dir Hozzáadja a dir könyvtárat azon listához, amelyekben az előfordító a #include <.> direktíva segítségével megadott header fájlokat keresi.
-cl-single-precision-constant A dupla pontosságú lebegőpontos konstansok egyszeres pontosságúként kezelése.
-cl-opt-disable Mindennemű optimalziálás kikapcsolása.
-cl-mad-enable Az a*b+c alakú konstrukciók helyettesítése a mad művelettel. Az eredmény gyorsabb kód lesz, de veszítünk a számértékek pontosságából.
-cl-no-signed-zeros Azt jelezhetjük vele, hogy a 0.0 érték előjelére nem építünk, ami kihasználható egyes optimalizálási műveleteknél.
-cl-unsafe-math-optimizations A pontosságot és a megfelelő IEEE szabványokat figyelmen kívül hagyó optimalizálási lehetőségeket kapcsolhatjuk be (magában foglalja a -cl-mad-enable és cl-no-signed-zeros kapcsolókat is).
-cl-finite-math-only A lebegőpontos aritmetikai műveletek optimalizálása, azzal a feltétellel, hogy biztosan nem jelennek meg NaN és inf értékek a program futása során.
-cl-std= Meghatározhatjuk vele az OpenCL C nyelv verzióját.
Lehetséges értékei: CL1.1 vagy CL1.2. Alapértelmezése az eszköz által támogatott OpenCL C
Kapcsoló Leírás
verzió, amelyet a clGetDeviceInfo függvénnyel le is kérdezhetünk.
-create-library Bekapcsolásával programkönyvtár készül a megadott programból.
Specifikáció:
cl_int clBuildProgram( cl_program program, cl_unit num_devices, const cl_device_id*
device_list,
const char* options, void (CL_CALLBACK*
pfn_notify)(cl_program program, void*
user_data),
void* user_data);
Paraméterek: program - Egy OpenCL C forráskódot tartalmazó program objektum.
num_devices - A device_list tömb elemeinek száma.
device_list - num_devices darab OpenCL eszköz azonosítóját tartalmazó tömb.
options - A fordítás és linkelés paramétereit tartalmazó karaktertömb.
pfn_notify - Egy alkalmas specifikációjú függvény, amelyet a clBuildProgram függvény akkor hív meg, amikor a fordítás és linkelés befejeződött. NULL paraméter esetén a clBuildProgram függvény blokkolja a program futását a fordítási folyamat befejeződéséig, nem NULL paraméter esetén nem blokkolja, a fordítás befejeztéről a pfn_notify függvényen keresztül értesülhetünk.
user_data - Ezen paramétert kapja meg a pfn_notify függvény második paramétereként.
Visszatérési érték: Sikeres végrehajtás esetén egy CL_SUCCESS, ellenkező esetben hibakód.
Ahogy az a függvény paramétereit áttekintve látható, a fordítás aszinkron módon is végrehajtható, ami gyorsíthatja a program végrehajtását. A clBuildProgram a paraméterként kapott eszközök mindegyikére elkészíti a megfelelő futtatható kódot, amely bekerül a szintén paraméterként kapott cl_program típusú program objektumba, num_devices eszköz esetén ez num_devices darab lefordított programot jelent. A fordítás és linkelés menetét az OpenCL C nyelvű forráskódok esetén is kapcsolókkal befolyásolhatjuk. A legfontosabb kapcsolókat a 7.5. táblázatban foglaljuk össze.
Ahogy azt a korábbiakban láthattuk, az OpenCL minden objektumhoz specifikál olyan függvényt, amellyel annak tulajdonságait kérdezhetjük le.
Program objektumok esetén ez a clGetProgramInfo.
7.6. táblázat - A program objektumok
clGetProgramInfofüggvénnyel lekérdezhető fontosabb tulajdonságai.
cl_program_info értéke Típus Leírás
CL_PROGRAM_CONTEXT cl_context A program objektum környezete.
CL_PROGRAM_NUM_DEVICES cl_uint A program objektumhoz rendelt OpenCL eszközök száma.
cl_program_info értéke Típus Leírás
CL_PROGRAM_DEVICES cl_device_id* A program objektumhoz rendelt OpenCL eszközök azonosítói.
CL_PROGRAM_SOURCE char* Az OpenCL C nyelvű forráskódja
egyetlen sztringként.
CL_PROGRAM_BINARY_SIZES size_t* A lefordított kódok méreteit tartalmazó tömb.
CL_PROGRAM_BINARIES char** A lefordított forráskódok.
CL_PROGRAM_NUM_KERNELS size_t A programban definiált kernelek száma.
CL_PROGRAM_KERNEL_NAMES char* A programban definiált kernelek nevei, pontosvesszővel elválasztva.
Specifikáció:
cl_int clGetProgramInfo( cl_program program,
cl_context_info param_name,
size_t param_value_size,
void* param_value, size_t*
param_value_size_ret);
Paraméterek: program - Egy program objektum.
param_name - A fontosabb lekérdezhető
tulajdonságokat a 7.6. táblázatban foglaljuk össze.
param_value_size - A függvény által param_value címre visszaadható bájtok száma.
param_value - Egy param_value_size bájt méretű lefoglalt memóriaterület címe.
param_value_size_ret - Ezen címre kerül be a ténylegesen visszaadott, azaz param_value címre másolt bájtok száma.
Visszatérési érték: Sikeres végrehajtás esetén CL_SUCCESS, ellenkező esetben hibakód.
A clGetProgramInfo függvény egyik leggyakoribb felhasználási módja az, amikor a lefordított kódokat egy bináris karakterfolyamként kérdezzük le. A kapott bináris kód fájlba írható és a későbbiekben a fájlból bármikor beolvasható, így ezzel megspórolhatjuk azt, hogy a párhuzamos végrehajtás előtt minden alkalommal le kelljen fordítani az esetleg igen nagy és összetett kernel kódot.
Program objektumokat korábban lefordított bináris kódokból a clCreateProgramWithBinary függvénnyel hozhatunk létre.
Specifikáció:
cl_program clCreateProgramWithBinary(
cl_context context,
cl_uint num_devices,
const cl_device_id* device_list,
const size_t* lengths,
const unsigned char** binaries,
cl_int* binary_status,
cl_int* errcode_ret);
Paraméterek: context - A megfelelő környezet objektum.
num_devices - A device_list tömb elemeinek száma.
device_list - Azon eszköz azonosítóinak tömbje, amelyekre lefordított kernel-kóddal rendelkezünk.
lengths - A bináris programkódot tartalmazó tömbök hosszainak num_devices méretű tömbje.
binaries - Bináris programkódok mutatóinak num_devices elemszámú tömbje. A paraméterek megadása során arra kell ügyelnünk, hogy a binaries[i] bináris kód hossza lengths[i] és a devices[i] azonosítójú eszközre lett lefordítva.
binary_status - num_devices elemszámú tömb, amelynek i. elemére a CL_SUCCESS értéket állítja be a függvény, ha az i. kód betöltése sikeresen megtörtént.
errcode_ret - Sikertelen végrehajtás esetén ezen címre kerül beállításra a hibakód.
Visszatérési érték: Sikeres végrehajtás esetén egy cl_program objektum és az errcode_ret értéke CL_SUCCESS, ellenkező esetben beállításra kerül a hibakód.
A lefordított, bináris kódok kezelésénél arra kell ügyelnünk, hogy a különböző eszközöknek különbözik az utasításkészlete, ezért több OpenCL eszköz használata esetén több különböző bináris kóddal kell dolgoznunk.
Akár forráskódból, akár bináris kódból hoztuk létre a program objektumot, a hozzá rendelt erőforrásokat a korábbiakhoz hasonló clReleaseProgram függvénnyel szabadíthatjuk fel.
Az alábbi példaprogramban kernelek forráskódjának olvasására/írására, fordítására, valamint bináris kódok olvasására és írására alkalmas függvények használatát szemléltetjük.
A kernel kódok olvasását most és a fejezet hátralévő részében a kernelio.h és kernelio.c fájlokban definiált függvények segítségével végezzük.