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
unsigned
típus, ami írhatóu
prefixszel.
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
half
afloat
és adouble
tí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_fp16
bő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_16
eseté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
clFinish
meghí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
clCreateCommandQueue
parancsban aCL_QUEUE_PROFILING_ENABLE
property-t. - Meg kell adni egy
cl_event
eseményt a profilozandó művelethez. - Le kell kérdezni a profilozás eredményét a
clGetEventProfilingInfo
fü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_WRITE
az alapértelmezett érték. - A
_HOST_PTR
vé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_PTR
aCL_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
flags
megadása megegyezik a buffer létrehozásánál használttal. - A
buffer_create_type
alapvetőenCL_BUFFER_CREATE_TYPE_REGION
. - A
buffer_create_info
egycl_buffer_region
struktú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_hello
pé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!