• Nem Talált Eredményt

2. Az OpenCL API eszközei

2.1. Az OpenCL modell

2.1.4. Programozási modell

Az alábbiakban összefoglaljuk azokat a lépéseket, melyeket implementáció szinten meg kell valósítanunk szinte minden OpenCL-t használó alkalmazásban.

1. Az OpenCL-t használatának logikailag első lépéseként fel kell térképeznünk a rendelkezésre álló hardverkörnyezetet, azonosítanunk kell az OpenCL platformokat és a hozzájuk tartozó eszközöket.

2. Következő lépésként létre kell hoznunk a párhuzamos végrehajtás során használandó környezet és parancssor objektumokat.

3. A következő logikai lépéseket tetszőleges sorrendben végrehajthatjuk:

• Be kell olvasnunk vagy le kell generálnunk az OpenCL eszközön futó kernel-kódot és le kell fordítanunk azt az OpenCL eszköz(ök)re. Alternatív módon beolvashatunk már korábban lefordított, bináris kernel-kódokat.

• Szükség esetén le kell foglalnunk az OpenCL eszközön a bemenő és kimenő adatok tárolására szolgáló puffereket és megfelelően fel kell töltenünk tartalommal.

4. Adatpárhuzamosításhoz az adathalmazhoz egy megfelelő indextartományt definiálunk, az indextartomány egyes elemei egy-egy munkaelemet azonosítanak és minden munkaelem az adathalmaz egy vagy több adatelemének feldolgozásáról gondoskodik. A munkaelemeket munkacsoportokba szervezzük, ha ezt nem tesszük meg, akkor az OpenCL hoz létre munkacsoportokat a munkaelemekből. A párhuzamos végrehajtás során az ütemezés alapegysége a munkacsoport. A végrehajtás első lépéseként minden számítási egység megkap egy végrehajtandó munkacsoportot. A számítási egység feldolgozó egységei a munkacsoporthoz tartozó munkaelemeket hajtják végre párhuzamosan. A munkaelemek száma természetesen meghaladhatja a számítási egységek feldolgozó egységeinek számát, ekkor megfelelő, round-robin3 ütemezéssel kerülnek végrehajtásra az egyes munkaelemek. Miután egy munkacsoporthoz tartozó összes munkaelem végrehajtása befejeződött, azaz a munkaelemekhez tartozó kernelek lefutottak az adathalmaz megfelelő részeire, a számítási egység újabb munkacsoportot kap végrehajtásra. Mindez addig ismétlődik, amíg minden munkaelem végrehajtásra nem került.

Az OpenCL lehetőséget biztosít funkcionális párhuzamosításra is, ekkor egyetlen munkaelem alkot egy munkacsoportot és ezt hajtja végre egy számítási egység egy feldolgozó egysége egy alkalommal.

5. Szükség esetén a végrehajtás befejeztével le kell töltenünk az OpenCL eszköz memóriájából ez eredményeket tartalmazó puffer tartalmát a gazdagép memóriájába.

A fenti felsorolás nagyon jól összefoglalja azt a stratégiát, amelyet a jegyzet hátralévő részében szinte minden példaprogramban megvalósítunk.

2.2. Fordítás

OpenCL-t használó alkalmazások fordításához/linkeléséhez szükségünk van az OpenCL header fájlokra, melyek beszerzésére több lehetőség is kínálkozik. A legegyszerűbb esetben a megfelelő driver-ek részeként érkeznek a header fájlok. Korábban például az NVidia fejlesztői driver-ének része volt, azonban nemrég kikerült belőle.

Ilyen esetben egyrészt letölthetjük a header fájlokat a Khronos csoport honlapjáról4. Másik lehetőségünk, hogy a megfelelő Linux-repository-ból telepítjük a header fájlokat (Ubuntu környezetekben például a opencl-headers csomag részeként érhetők el).

A header fájlok mellett szükségünk van a specifikált függvények implementációit tartalmazó programkönyvtárra, amit az OpenCL eszköz gyártójának feladata biztosítani: NVidia eszközök esetén például mindig része az aktuális driver-nek, így telepítve azt, megjelenik a fájlrendszeren egy megfelelő nevű programkönyvtár, Linux környezetben a libOpenCL.so. Ha a header fájlok és a programkönyvtár jelenlétéről is meggyőződtünk, az alábbihoz hasonló CMakeLists.txt konfigurációs állományt használhatunk:

7.1. példa - CMakeLists.txt

CMAKE_MINIMUM_REQUIRED(VERSION 2.8) PROJECT(helloWorld)

AUX_SOURCE_DIRECTORY(. SRC)

ADD_EXECUTABLE(${PROJECT_NAME} ${SRC})

TARGET_LINK_LIBRARIES(${PROJECT_NAME} OpenCL)

3A round-robin ütemezés az egyik legegyszerűbb folyamat/szál ütemezési algoritmus: a párhuzamosan futó kódok rendre egy megkapják a processzort egy rövid időintervallumra, amely alatt néhány gépi kódú utasítást el tudnak végezni, majd továbbadják a processzort valamely következő kódnak.

4http://www.khronos.org/registry/cl/

SET(CMAKE_VERBOSE_MAKEFILE ON)

A CMakeLists.txt állomány könnyen értelmezhető, azonban kihasználtuk azt, hogy Linux környezetben a gcc fordítóprogram a /usr/include és /usr/lib könyvtárakban keresi alapértelmezetten az OpenCL header-fájlokat és a programkönyvtárat. Ez a feltételezés azonban nem feltétlenül áll más operációs rendszerek, például Windows vagy OS-X esetén, és akár Linux környezetben is előfordulhatnak olyan driver-ek, amelyek nem az alapértelmezett helyeken helyezik el az OpenCL header-fájlokat és programkönyvtárat. A cmake eszközrendszer elegáns megoldást biztosít erre a problémára, azáltal, hogy úgynevezett kereső-modulokat írhatunk hozzá, amelyek elfedik az operációs rendszerek különbségeit és megpróbálják megtalálni a szükséges header-fájlokat, illetve programkönyvtárakat az aktuális környezetben. A kereső-modulok készítéséről részletes leírás található a cmake weboldalán, az alábbiakban csak közöljük és bemutatjuk használatát a szerző által készített OpenCL-t kereső cmake modulnak.

7.2. példa - FindOpenCL.cmake

# FindOpenCL.cmake

# sets OPENCL_INCLUDE_DIR, OPENCL_LIBRARY, OPENCL_FOUND

# Finding header files FIND_PATH(OPENCL_INCLUDE_DIR NAMES

CL/cl.h OpenCL/cl.h PATHS

$ENV{AMDAPPSDKROOT}/include $ENV{INTELOCLSDKROOT}/include

$ENV{NVSDKCOMPUTE_ROOT}/OpenCL/common/inc $ENV{ATISTREAMSDKROOT}/include)

# Setting possible library paths IF(CMAKE_SIZEOF_VOID_P EQUAL 4) # 32bit architecture

SET(OPENCL_LIB_SEARCH_PATH ${OPENCL_LIB_SEARCH_PATH}

$ENV{AMDAPPSDKROOT}/lib/x86 $ENV{INTELOCLSDKROOT}/lib/x86

$ENV{NVSDKCOMPUTE_ROOT}/OpenCL/common/lib/Win32 $ENV{ATISTREAMSDKROOT}/lib/x86)

ELSEIF(CMAKE_SIZEOF_VOID_P EQUAL 8) # 64bit architecture

SET(OPENCL_LIB_SEARCH_PATH ${OPENCL_LIB_SEARCH_PATH}

$ENV{AMDAPPSDKROOT}/lib/x86_64 $ENV{INTELOCLSDKROOT}/lib/x64

$ENV{NVSDKCOMPUTE_ROOT}/OpenCL/common/lib/x64 $ENV{ATISTREAMSDKROOT}/lib/x86_64)

ENDIF(CMAKE_SIZEOF_VOID_P EQUAL 4)

# Finding libraries FIND_LIBRARY(

OPENCL_LIBRARY NAMES OpenCL

PATHS ${OPENCL_LIB_SEARCH_PATH})

# Handling REQUIRED and QUIET args INCLUDE(FindPackageHandleStandardArgs)

IF (OPENCL_INCLUDE_DIR AND OPENCL_LIBRARY ) SET (_OPENCL_VERSION_TEST_SOURCE

" #if __APPLE__

#include <OpenCL/cl.h>

#else /* !__APPLE__ */

#include <CL/cl.h>

#endif /* __APPLE__ */

#include <stdio.h>

#include <stdlib.h>

int main() {

char *version;

cl_int result;

cl_platform_id id;

size_t n;

result = clGetPlatformIDs(1, &id, NULL);

if (result == CL_SUCCESS) {

result = clGetPlatformInfo(id, CL_PLATFORM_VERSION, 0, NULL, &n);

if (result == CL_SUCCESS) {

version = (char*)malloc(n * sizeof(char));

result = clGetPlatformInfo(id, CL_PLATFORM_VERSION, n, version, NULL);

if (result == CL_SUCCESS) printf(\"%s\", version);

free(version);

} }

return result == CL_SUCCESS ? EXIT_SUCCESS : EXIT_FAILURE;

}")

# Writing test application into file SET (_OPENCL_VERSION_SOURCE

"${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp/openclversion.c") FILE (WRITE ${_OPENCL_VERSION_SOURCE} "${_OPENCL_VERSION_TEST_SOURCE}\n") # Compile and run test application

TRY_RUN (_OPENCL_VERSION_RUN_RESULT _OPENCL_VERSION_COMPILE_RESULT ${CMAKE_BINARY_DIR} ${_OPENCL_VERSION_SOURCE}

RUN_OUTPUT_VARIABLE _OPENCL_VERSION_STRING

CMAKE_FLAGS "-DINCLUDE_DIRECTORIES:STRING=${OPENCL_INCLUDE_DIRS}"

"-DLINK_LIBRARIES:STRING=${OPENCL_LIBRARY}") # Print status

IF (_OPENCL_VERSION_RUN_RESULT EQUAL 0) MESSAGE(STATUS ${_OPENCL_VERSION_STRING}) ELSEIF (_OPENCL_VERSION_RUN_RESULT EQUAL 0) MESSAGE(WARNING "Running test program failed.") ENDIF (_OPENCL_VERSION_RUN_RESULT EQUAL 0)

ENDIF (OPENCL_INCLUDE_DIR AND OPENCL_LIBRARY)

A fájl tartalmát nem értelmezzük sorról sorra, mert haladó cmake eszközöket is tartalmaz. Nagy vonalakban azonban előbb a header-fájlokat keresi az egyes gyártók driver-ei által gyakran használt könyvtárakban, majd hasonló módon a programkönyvtárakat, amelyek helye azonban nem csak különböző operációs rendszereken, de 32 vagy 64 bites architektúrákon is különbözhet. Ha sikerült a header-fájlokat és a programkönyvtárat is megtalálnia, a openclversion.c forrásfájlba ír egy egyszerű OpenCL-t használó kódot, s megpróbálja fordítani, majd futtatni azt. Ha a program visszatérési értéke sikeres végrehajtást jelez, azaz 0, akkor kiírjuk a kimenetre státusz üzenetként a program szöveges kimenetét, vagyis az azonosított OpenCL platform verzióját.

Ellenkező esetben hibaüzenetet írunk a kimenetre. A szkript lényege, hogy futása során megfelelő módon beállítja az OpenCL header-fájlok elérési útját tartalmazó OPENCL_INCLUDE_DIR, a programkönyvtár elérési útját tartalmazó OPENCL_LIBRARY és az OpenCL jelenlétét jelző OPENCL_FOUND változók értékét, így azokat a hívási környezetben megfelelően felhasználhatjuk. A hívási környezet esetünkben a CMakeLists.txt állomány, amelyet természetesen némileg át kell alakítanunk.

7.3. példa - CMakeLists.txt

CMAKE_MINIMUM_REQUIRED(VERSION 2.8) PROJECT(helloWorld)

AUX_SOURCE_DIRECTORY(. SRC)

ADD_EXECUTABLE(${PROJECT_NAME} ${SRC})

SET(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/.") FIND_PACKAGE(OpenCL REQUIRED)

MESSAGE(STATUS "OpenCL_Found: ${OPENCL_FOUND}")

TARGET_LINK_LIBRARIES(${PROJECT_NAME} ${OPENCL_LIBRARY}) INCLUDE_DIRECTORIES(${OPENCL_INCLUDE_DIR})

SET(CMAKE_VERBOSE_MAKEFILE ON)

A kereső-modul használatához meg kell adnunk a kereső-modul kódját tartalmazó könyvtárat a CMAKE_MODULE_PATH változó segítségével. Ezt követően az OpenCL-t kereső cmake-modult a FIND_PACKAGE(OpenCL REQUIRED) makró segítségével futtathatjuk le. A makró az OpenCL sztringből tudja, hogy FindOpenCL.cmake nevű kereső-modul implementációt kell keresnie, a REQUIRED kulcsszó pedig arra utal, hogy a könyvtár nélkülözhetetlen alkalmazásunk lefordításához. Ha a modul nem találja meg az OpenCL header-fájlokat vagy implementációt, a konfiguráció futási hibával megáll. A FIND_PACKAGE lefutása során beállításra kerülnek a korábban már jelzett OPENCL_LIBRARY, OPENCL_INCLUDE_DIR és OPENCL_FOUND változók, melyeket a megfelelő módon használhatunk fel a header-fájlok elérési útjának, valamint a linkelendő programkönyvtár megadása során.