Memóriakezelés
Adattípusok
A C nyelv szokásos típusai, megkötésekkel és kiegészítésekkel.
Skalár típusok
| Típus | Leírás |
|---|---|
bool |
Logikai érték: false (0), true (1) |
char |
Előjeles 8 bites egész |
uchar |
Praktikusan a byte típus |
short |
16 bites egész |
int |
32 bites egész |
long |
64 bites egész |
half |
16 bites lebegőpontos érték, IEEE-754-2008 |
float |
32 bites lebegőpontos érték, IEEE-754 |
intptr_t |
Mutatóvá konvertálható előjeles egész |
uintptr_t |
Mutatóvá konvertálható előjel nélküli egész |
size_t |
A sizeof operátor által visszaadott típus |
void |
Hiányzó, tetszőleges típus |
- Szigorúbb/szabványosított a bitek száma.
- Az egészekből van
unsignedtípus, ami írhatóuprefixszel.
Byte sorrend
Az OpenCL szabvány sajnos nem tudta rögzíteni a byte sorrendet a különféle eszközök miatt.
Dupla pontosság
A double típus általánosan nem tekinthető támogatottnak. Bővítmény formájában elérhető:
#pragma OPENCL EXTENSION cl_khr_fp64 : enable
Bővítmények bekapcsolása
Az összes bővítményt egyidejűleg az all néven aktiválhatjuk.
A kernel kódjában a következő formában ellenőrízhetjük, hogy elérhető-e a double típus.
#ifdef FP_64
double x = 0.0;
#else
float x = 0.0;
#endif
Kerekítési módok:
- a legközelebbi értékhez,
- mínusz végtelenhez,
- plusz végtelenhez,
- 0-hoz (truncation).
Fél pontosság
- A
halfafloatés adoubletípusoknál újabb típus. - Az Nvidia vezette be a Cg nyelv részeként.
- Az OpenGL és a Direct3D is átvette.
- A
cl_khr_fp16bővítmény jelzi, hogy elérhető-e.
Részei:
- 1 bit: előjel bit
- 5 bit: exponens (15-tel eltolva)
- 10 bit: törtrész
Vektor típusok
A következő típusok vektorként használhatók, hogy ha kiegészítjük a típus nevét a 2, 3, 4, 8, 16 számértékek valamelyikével.
char short int long uchar ushort uint ulong float
Inicializálás
Hasonlóképpen inicializáható, mint a tömbök, csak kerek zárójellel:
float4 coord = (float4)(1.0, 2.0, 3.0, 1.0);
Vektorok segítségével is inicializálható.
float2 a = (float2)(1.0, 2.0);
float2 b = (float2)(3.0, 1.0);
float4 coord = (float4)(a, b);
Skalárokkal együtt vegyesen is használhatók.
Komponensek elérése
Indexelés .sN formában (ahol N egy hexadecimális számjegy)
coord.s2
Név szerinti elérés
coord.x, coord.y, coord.z, coord.w
Részek szerinti elérés
coord.hi, coord.lo, coord.even, coord.odd
Több komponens egyidejű hivatkozása
coord.yw
coord.s03
Az így kijelölt tartományoknak az inicializáláshoz hasonlóan adható meg egyidejűleg több érték is.
Preferált vektor méretek
A device-nak típusonként (a hardveres kivitelezésből adódóan) van preferált mérete a vektorok elemszámára vonatkozóan.
cl_uint preferred_width;
clGetDeviceInfo(
device,
CL_DEVICE_PREFERRED_VECTOR_WIDTH_FLOAT,
sizeof(preferred_width),
&preferred_width,
NULL
);
- A preferált mérethez igazodás előnyös számítási teljesítmény szempontjából.
- A host kódban, fordításnál paraméterként átadhatjuk a preferált méretet, például a
-DPREF_VECT_SIZE_16esetén:
#if PREF_VECT_SIZE_16
// ...
#endif
- Külön kerneleket is létrehozhatunk device-nak megfelelően (amelynek a nevében jelezhetjük a preferált méretet).
OpenCL C fordító
clBuildProgram(
cl_program program,
cl_uint 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)
)
Példa
Nézzük meg az examples/02_compilation példát!
Típusok
Próbáljuk ki, hogy valóban működnek az előzőekben bemutatott típusok!
Fordító opciók
| Opció | Leírás |
|---|---|
-cl-std=VERSION |
OpenCL verzió beállítása |
-D NAME |
Makró beállítás 1 értékre |
-D NAME=VALUE |
Makró beállítás VALUE értékre |
-Werror |
Minden figyelmeztetést hibának tekint |
Makró átadása
Nézzük meg, hogy valóban át tudunk adni értéket fordító opción keresztül!
Program információk
cl_int clGetProgramInfo(
cl_program program,
cl_program_info param_name,
size_t param_value_size,
void *param_value,
size_t *param_value_size_ret
)
Bináris mérete
Kérdezzük le az elkészített binárisok számát és méretüket!
Fordító kimenete
cl_int clGetProgramBuildInfo(
cl_program program,
cl_device_id device,
cl_program_build_info param_name,
size_t param_value_size,
void *param_value,
size_t *param_value_size_ret
)
Hibák kiíratása
Hibás fordítás esetén írassuk ki, hogy mi a probléma!
Hibakeresés
A kernel kódján belül használhatjuk a printf-et.
- A
clFinishmeghívás hatására kerül kiíratásra a kernelekhez tartozó kimenet. (Ez magától is meghívódik.) - A kiírások sorrendje egyáltalán nem garantált.
My ID
Írassuk ki a kerneleken belül a saját azonosítóikat!
Profilozás
A profilozás események segítségével valósítható meg.
Lépései:
- Be kell állítani a
clCreateCommandQueueparancsban aCL_QUEUE_PROFILING_ENABLEproperty-t. - Meg kell adni egy
cl_eventeseményt a profilozandó művelethez. - Le kell kérdezni a profilozás eredményét a
clGetEventProfilingInfofüggvénnyel.
cl_int clGetEventProfilingInfo(
cl_event event,
cl_profiling_info param_name,
size_t param_value_size,
void *param_value,
size_t *param_value_size_ret
)
Beállítható cl_profiling_info paraméterek:
| Név | Leírás |
|---|---|
CL_PROFILING_COMMAND_QUEUED |
A parancs bekerült a sorba |
CL_PROFILING_COMMAND_SUBMIT |
A parancs kiküldésre került a device-ra |
CL_PROFILING_COMMAND_START |
A parancs végrehajtása elkezdődött |
CL_PROFILING_COMMAND_END |
A parancs végrehajtása befejeződött |
A lekérdezett paraméter értéke cl_ulong típusként, nanoszekundumban van megadva.
Blokkolás
A clFinish használatára szükség lehet, hogy a profilozandó parancs le tudjon futni a lekérdezés előtt!
Példa
Nézzük meg az examples/03_profiling példát!
Buffer létrehozása
cl_mem clCreateBuffer(
cl_context context,
cl_mem_flags flags,
size_t size,
void *host_ptr,
cl_int *errcode_ret
)
A cl_mem_flags lehetséges értékei:
| Érték | Leírás |
|---|---|
CL_MEM_READ_WRITE |
Olvasható és írható |
CL_MEM_WRITE_ONLY |
Csak írható |
CL_MEM_READ_ONLY |
Csak olvasható |
CL_MEM_USE_HOST_PTR |
A device a host memóriáját használja |
CL_MEM_COPY_HOST_PTR |
A device másolatot készít a host memóriájáról |
CL_MEM_ALLOC_HOST_PTR |
Egy nem kilapozható memóriát allokál |
Memória flag-ek
A bufferhez megadott flag-ek (cl_mem_flags) a device-ra vonatkoznak.
A helytelen hozzáférés nem definiált működést eredményez.
- A
CL_MEM_READ_WRITEaz alapértelmezett érték. - A
_HOST_PTRvégződésűek az allokáció módját írják le. - A flag-eket kombinációban lehet használni, például:
CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR. - A másolás számítási időt, plusz memória allokálást, de gyorsabb elérést eredményez.
- A
CL_MEM_ALLOC_HOST_PTRaCL_MEM_COPY_HOST_PTR-el együtt használható. - A csak írható memória esetében nem kell beállítani a
host_ptrértéket (lehetNULL).
Részbufferek létrehozása
A bufferek bizonyos részeit más kernelek számára is elérhetővé tudjuk tenni.
cl_mem clCreateSubBuffer(
cl_mem buffer,
cl_mem_flags flags,
cl_buffer_create_type buffer_create_type,
const void *buffer_create_info,
cl_int *errcode_ret
)
Paraméterei:
- A
flagsmegadása megegyezik a buffer létrehozásánál használttal. - A
buffer_create_typealapvetőenCL_BUFFER_CREATE_TYPE_REGION. - A
buffer_create_infoegycl_buffer_regionstruktúrára mutat, amely gyakorlatilag egy offset és méret párt ír le.
Például egy int típusú elemeket tartalmazó bufferből a [400, 499] indextartomány kijelölése így adható meg:
cl_buffer_region region;
region.origin = 300 * sizeof(int);
region.size = 100 * sizeof(int);
Buffer adatok lekérdezése
A bufferek adatait a clGetMemObjectInfo függvénnyel kérdezhetjük le.
cl_int clGetMemObjectInfo(
cl_mem memobj,
cl_mem_info param_name,
size_t param_value_size,
void *param_value,
size_t *param_value_size_ret
)
A param_name néhány lehetséges értéke:
| Név | Leírás |
|---|---|
CL_MEM_FLAGS |
A memória objektumhoz tartozó flag-eket adja vissza |
CL_MEM_HOST_PTR |
A host memóriaterület címét adja vissza (void* típussal) |
CL_MEM_SIZE |
A memóriaterület méretét adja vissza (size_t típussal) |
Példa
Nézzük meg az examples/04_buffers példát!
Command Queue parancsok
A host és a device között az adatokat a clEnqueueReadBuffer és clEnqueueWriteBuffer parancsokkal lehet mozgatni:
A létrehozott bufferek között is lehet másolási műveletet végrehajtani a clEnqueueCopyBuffer paranccsal:
Feladatok
1. Hibakódok
- Keressük meg, hogy mely fájlban kerültek definiálásra az OpenCL hibakódok!
- Definiáljunk egy függvényt, amelyik visszaadja a hibakód nevét!
2. Hibakezelés
Készítsünk programokat például a következő hibalehetőségekre:
- túl nagy méretű kernel program,
- 0-val osztás,
- OpenCL C nyelvi limitációk.
Vizsgáljuk meg a kimeneteket!
- Próbáljuk meg kiszervezni a kernel fordításához, fordító kimenetének eléréséhez szükséges programrészeket egy függvénybe!
3. Profilozás
Készítsünk programot az egyes programrészek futási idejének méréséhez!
- Válasszunk megfelelő mértékegységet!
- Jelenítsük meg grafikonon, hogy a program mennyi időt tölt az egyes műveletekkel a
01_hellopélda esetében!
4. Mátrix műveletek
Implementáljuk a következő mátrix műveleteket OpenCL segítségével!
- Transzponálás
- Szorzás
- Oszlopösszeg számítás
- Sorösszeg számítás
(A mátrix megadásához használjunk sor- vagy oszlopfolytonos tárolási módot.)
5. Profilozás, optimalizálás
Az eddigi számítási feladatok esetében végezzünk méréseket és optimalizáljuk a programot!
- Próbáljunk meg lokális és privát memória használatával javítani a számítási teljesítményt!
- Optimalizáljuk a munkacsoportok és munkaelemek számát!
- Mérjük le az adatok mozgatásához szükséges időt!
- Készítsünk méréseket a komplexitás becsléséhez!
- Ábrázoljuk, összegezzük a kapott eredményeket!