alusta \ valmistaja | Nvidia | AMD | Intel |
---|---|---|---|
OpenCL 1.2 | Tuettu | Tuettu | Tuettu |
OpenCL 2.0 | Ei tuettu | Tuettu | Rajoitettu tuki |
CUDA | Tuettu | Ei tuettu | Ei tuettu |
__global
)__local
)__constant
)(__private
)CL/cl.hpp
-otsikkotiedostonmain.cpp
), jossa tarkistetaan käytettyjen parametrien sopivuus käytetylle OpenCL-laitteella ja käsitellään kaikki virhetilanteetmain.cpp
-tiedosto alkaa CL/cl.hpp
-otsikkotiedoston sisällyttämisellä:#include <CL/cl.hpp>
cl
-nimiavaruuteenusing namespace cl
-komentoa. Otamme sen kuitankin mukaan tässä esimerkissä selvyyden vuoksi.VECTOR_CLASS
ja STRING_CLASS
viittaavat oletuksena std::vector
ja std::string
tietotyyppeihincl_int
-tyyppisen virhekoodin taicl_int
-tyyppiseen virhekoodimuuttujaan, johon OpenCL-kutsun status kirjoitetaanCL_SUCCESS
(0
) on geneerinen kaikki ok -paluuarvoCL_DEVICE_NOT_FOUND / -1
,CL_OUT_OF_RESOURCES / -5
,CL_INVALID_COMMAND_QUEUE / -37
,CL_INVALID_KERNEL_ARGS / -52
CL/cl.h
-tiedostosta#define __CL_ENABLE_EXCEPTIONS
#include <CL/cl.hpp>
try-catch
-rakenteella:try {
...
} catch(const cl::Error &err) {
std::cerr << "Virheviesti: " << err.what() << std::endl;
}
cl::Platform
-luokan sisälle, joka tarjoaa staattisen jäsenfunktion OpenCL-alustojen hakemiseen:static cl_int cl::Platform::get(VECTOR_CLASS<Platform> * platforms)
cl_int err;
std::vector<cl::Platform> platforms;
err = cl::Platform::get(&platforms);
if(err != CL_SUCCESS) {
std::cerr << "OpenCL-alustojen hakeminen epäonnistui." <<
std::endl;
std::cerr << "OpenCL-virhekoodi: " << err << std::endl;
return 1;
}
if(platforms.size() < 1) {
std::cerr << "OpenCL-alustoja ei löytynyt." << std::endl;
return 1;
}
getInfo
-jäsenfunktion avulla:cl_int cl::Platform::getInfo(cl_platform_info name,
STRING_CLASS * param)
CL_PLATFORM_VERSION
-lipulla ja valmistajan nimen saa selville CL_PLATFORM_VENDOR
- lipullaCL_PLATFORM_EXTENSIONS
-lippu palauttaa listan tuetuista laajennuksista.cl::Device
-luokan sisään ja cl::Platform
-luokka sisältää getDevices
-jäsenfunktion OpenCL-laitteiden kyselemistä varten:cl_int cl::Platform::getDevices(cl_device_type type,
VECTOR_CLASS<Device> * devices)
type
-argumentti voi saada esimerkiksi arvot CL_DEVICE_TYPE_CPU
, CL_DEVICE_TYPE_GPU
tai CL_DEVICE_TYPE_ACCELERATOR
. Tämän kurssin puitteissa CL_DEVICE_TYPE_GPU
on tietenkin kaikkein mielenkiintoisin vaihtoehto.std::vector<cl::Device> devices;
err = platforms[0].getDevices(CL_DEVICE_TYPE_GPU, &devices);
if(err != CL_SUCCESS) {
std::cout << "OpenCL-laitteiden hakeminen epäonnistui." <<
std::endl;
std::cerr << "OpenCL-virhekoodi: " << err << std::endl;
return 1;
}
if(devices.size() < 1) {
std::cout << "OpenCL-laitteita ei löytynyt." << std::endl;
return 1;
}
cl::Device
-oliolta eri asioita:template <typename T>
cl_int cl::Device::getInfo(cl_device_info name,
T * param)
CL_DEVICE_MAX_WORK_GROUP_SIZE
-lippu palauttaa säieryhmän suurimman mahdollisen koonCL_DEVICE_MAX_MEM_ALLOC_SIZE
-lippu palauttaa suurimman varattavissa olevan globaalin muistialueen koonCL_DEVICE_LOCAL_MEM_SIZE
-lippu palauttaa suurimman varattavissa olevan lokaalin muistialueen koonCL_DEVICE_EXTENSIONS
-lippu palauttaa OpenCL-laitteen tukeman laajennuksetcl::Context
-luokan sisään, jonka muodostinfunktio ottaa argumenttinaan mukaan liitettävät OpenCL-laiteet:cl::Context::Context(VECTOR_CLASS<Device>& devices,
cl_context_properties * properties = NULL,
void (CL_CALLBACK * pfn_notify)(
const char * errorinfo,
const void * private_info,
::size_t cb,
void * user_data) = NULL,
void * user_data = NULL,
cl_int * err = NULL)
std::vector<cl::Device> oneDevice;
oneDevice.push_back(devices[0]);
cl::Context context(oneDevice, 0, 0, 0, &err);
if(err != CL_SUCCESS) {
std::cout << "OpenCL-kontekstin luominen epäonnistui." <<
std::endl;
std::cerr << "OpenCL-virhekoodi: " << err << std::endl;
return 1;
}
cl::Context
-olio voidaan luoda myöskin ilman cl::Device
-oliota:cl::Context::Context(cl_device_type type,
cl_context_properties * properties = NULL,
void (CL_CALLBACK * pfn_notify)(
const char * errorinfo,
const void * private_info,
::size_t cb,
void * user_data) = NULL,
void * user_data = NULL,
cl_int * err = NULL)
type
argumenttia vastaavat laitetyypitgetInfo
-jäsenfunktiolla:template <typename T>
cl_int cl::Context::getInfo(cl_context_info name,
T * param)
CL_CONTEXT_DEVICES
-lippu, joka palauttaa kontekstiin liitetyt OpenCL-laiteet, on hyödyllinen mikäli cl::Context
-olio luotiin ilman cl::Device
-oliota.buffer
) kokonaislukuja (n
kpl) ja lisää jokaiseen alkioon luvun yksi:__kernel void add_one(__global int *buffer, int n) {
const int global_id = get_global_id(0);
if(global_id < n)
buffer[global_id]++;
}
__kernel
-avainsana kertoo, että kyseessä on ydin, jota voidaan kutsua isäntäohjelman puolelta__global
-avainsana kertoo, että buffer
-taulukko on tallennettu globaaliin muistiinget_global_id
-aliohjelman avulla. Aliohjelmalle annettu argumentti määrää indeksi dimension:const int global_id = get_global_id(0);
if(global_id < n)
buffer[global_id]++;
const char *kernel =
"__kernel void add_one(__global int *buffer, int n) { \n" \
" const int global_id = get_global_id(0); \n" \
" if(global_id < n) \n" \
" buffer[global_id]++; \n" \
"} \n";
cl::Program::Sources
-tyyppisen olion sisälle:cl::Program::Sources sources;
sources.push_back(
cl::Program::Sources::value_type(kernel, strlen(kernel)));
xxd
-työkalu kelpaa tähän tarkoitukseen loistavastikernel.cl
-tiedostoon. Tällöin komento xxd -i kernel.cl > kernel.cl.dat
tuottaisi seuraavan tiedoston:unsigned char kernel_cl[] = {
0x0a, 0x2f, 0x2f, 0x20, 0x59, 0x64, 0x69, 0x6e, 0x2c, ...
0x6b, 0x61, 0x20, 0x6f, 0x74, 0x74, 0x61, 0x61, 0x20, ...
...
0x2b, 0x3b, 0x20, 0x0a, 0x7d
};
unsigned int kernel_cl_len = 857;
#include "kernel.cl.dat"
...
cl::Program::Sources sources;
sources.push_back(cl::Program::Sources::value_type(
(const char*) kernel_cl, kernel_cl_len));
xxd
-työkalun hyödyntämisen lisäksi ytimien lähdekoodi voitaisiin ladata erillisestä tekstitiedostosta ajonaikaisesti merkkijonotaulukkoncl::Program
-luokan sisälle, jonka muodostinfunktio ottaa argumenttinaan liittyvän OpenCL-kontekstin ja ytimien lähdekoodit:cl::Program::Program(const Context& context,
const Sources& sources,
cl_int * err = NULL)
cl::Program program(context, sources, &err);
if(err != CL_SUCCESS) {
std::cout << "Ohjelma-objektin luominen epäonnistui." <<
std::endl;
std::cerr << "OpenCL-virhekoodi: " << err << std::endl;
return 1;
}
cl::Program
-luokan build
-jäsenfunktion avulla:cl_int cl::Program::build(const VECTOR_CLASS<Device>& devices,
const char * options = NULL,
void (CL_CALLBACK * pfn_notify)(
cl_program,
void * user_data) = NULL,
void * data = NULL)
CL_PROGRAM_BUILD_LOG
-lippu ´getBuildInfo´-jäsenfunktiolle:template <typename T>
cl_int cl::Program::getBuildInfo(cl_program_build_info name,
T * param)
err = program.build(oneDevice, 0);
if(err != CL_SUCCESS) {
std::string log;
program.getBuildInfo(oneDevice[0],
CL_PROGRAM_BUILD_LOG, &log);
std::cout <<
"OpenCL-kääntäjän tuloste:" << std::endl <<
log << std::endl;
std::cout <<
"Ytimien lähdekoodin kääntäminen epäonnistui." <<
std::endl;
std::cerr << "OpenCL-virhekoodi: " << err << std::endl;
return 1;
}
cl::Program::Sources
-olion luomisen kokonaan pois antamalla lähdekoodit suoraan merkkijonona cl::Program
-luokan muodostinfunktiolle:cl::Program::Program(const Context& context,
const STRING_CLASS& source,
bool build,
cl_int * err = NULL)
build
argumentin arvolla CL_TRUE
, käännettäisiin ytimien lähdekoodit jo cl::Program
-olion luonnin yhteydessä, jolloin meidän ei tarvitsisi kutsua build
-jäsenfunktiota erikseencl::Kernel
-luokan sisälle, jonka muodostinfunktio ottaa argumenttinaan Ohjelma-objektin ja ytimen nimen:cl::Kernel::Kernel(const Program& program,
const char * name,
cl_int * err = NULL)
cl::Kernel kernel(program, "add_one", &err);
if(err != CL_SUCCESS) {
std::cout << "Ytimen luominen epäonnistui." << std::endl;
std::cerr << "OpenCL-virhekoodi: " << err << std::endl;
return 1;
}
template<typename T0,
typename T1 = detail::NullType,
...,
typename T31 = detail::NullType>
struct make_kernel::detail::functionImplementation<T0, T1, ..., T31>
cl::make_kernel::make_kernel(const Program &program,
const STRING_CLASS name,
cl_int *err = NULL)
auto kernel = cl::make_kernel<cl::Buffer&, int>(program, "add_one");
cl::CommandQueue
-luokan sisälle, jonka muodostinfunktio ottaa argumenttinaan OpenCL-kontekstin ja OpenCL-laitteen, joihin komentojono on tarkoitus liittää:cl::CommandQueue::CommandQueue(
const Context& context,
const Device& device,
cl_command_queue_properties properties = 0,
cl_int * err = NULL)
cl::CommandQueue queue(context, oneDevice[0], 0, &err);
if(err != CL_SUCCESS) {
std::cout << "Komentojonon luominen epäonnistui." << std::endl;
std::cerr << "OpenCL-virhekoodi: " << err << std::endl;
return 1;
}
cl::Buffer
-luokan sisälle, joka ottaa argumenttinaan liittyvän OpenCL-kontekstin, flags
-lippumuuttujan ja puskurin koon:cl::Buffer::Buffer(
const Context& context,
cl_mem_flags flags,
::size_t size,
void * host_ptr = NULL,
cl_int * err = NULL)
cl::Buffer deviceBuffer(context, CL_MEM_READ_WRITE, N*sizeof(int), 0, &err);
if(err != CL_SUCCESS) {
std::cout << "Muistin varaaminen epäonnistui epäonnistui." << std::endl;
std::cerr << "OpenCL-virhekoodi: " << err << std::endl;
return 1;
}
enqueueWriteBuffer
-jäsenfunktio asetaa komentojonoon käskyn kirjoittaa dataa isäntälaiteen muistista Puskuri-objektiinblocking_write
-lipun asettaminen arvoon CL_TRUE
tekee kutsusta blockaavan eli aliohjelmasta palataan vasta kun siirto on suoritettu loppuuncl_int cl::CommandQueue::enqueueWriteBuffer(
const Buffer& buffer,
cl_bool blocking_write,
::size_t offset,
::size_t size,
const void * ptr,
const VECTOR_CLASS<Event> * events = NULL,
Event * event = NULL)
err = queue.enqueueWriteBuffer(deviceBuffer, CL_FALSE, 0, N*sizeof(int),
hostBuffer, 0, 0);
if(err != CL_SUCCESS) {
std::cout << "Isäntälaite -> OpenCL-laite -siirtokäskyn asettaminen " \
"komentojonoon epäonnistui." << std::endl;
std::cerr << "OpenCL-virhekoodi: " << err << std::endl;
}
setArg
-jäsenfunktiolla:template <typename T>
cl_int cl::Kernel::setArg(cl_uint index, T value)
kernel.setArg(0, deviceBuffer);
if(err != CL_SUCCESS) {
std::cout << "Ytimen 1. argumentin asettaminen epäonnistui." <<
std::endl;
std::cerr << "OpenCL-virhekoodi: " << err << std::endl;
return 1;
}
kernel.setArg(1, N);
if(err != CL_SUCCESS) {
std::cout << "Ytimen 2. argumentin asettaminen epäonnistui." <<
std::endl;
std::cerr << "OpenCL-virhekoodi: " << err << std::endl;
return 1;
}
enqueueNDRangeKernel
-jäsenfunktiollaglobal
-argumentilla ja säieryhmän koko vastaavasti local
-argumentillacl_int cl::CommandQueue::enqueueNDRangeKernel(
const Kernel& kernel,
const NDRange& offset,
const NDRange& global,
const NDRange& local,
const VECTOR_CLASS<Event> * events = NULL,
Event * event = NULL)
::size_t maxWorkGroupSize;
oneDevice[0].getInfo(CL_DEVICE_MAX_WORK_GROUP_SIZE, &maxWorkGroupSize);
if(err != CL_SUCCESS) {
std::cout << "OpenCL-laitteen suurimman mahdollinen säieryhmän koon "\
"kysyminen epäonnistui." << std::endl;
std::cerr << "OpenCL-virhekoodi: " << err << std::endl;
return 1;
}
::size_t workGroupCount = N/maxWorkGroupSize+1;
cl::NDRange globalDim(workGroupCount*maxWorkGroupSize, 1, 1);
cl::NDRange localDim(maxWorkGroupSize, 1, 1);
err = queue.enqueueNDRangeKernel(kernel, cl::NullRange,
globalDim, localDim, 0, 0);
if(err != CL_SUCCESS) {
std::cout << "Ytimen käynnistyskäskyn asettaminen komentojonoon " \
"epäonnistui." << std::endl;
std::cerr << "OpenCL-virhekoodi: " << err << std::endl;
return 1;
}
enqueueReadBuffer
-jäsenfunktio asetaa komentojonoon käskyn lukea dataa Puskuri-objektista isäntälaiteen muistiinblocking_read
-lipun asettaminen arvoon CL_TRUE
tekee kutsusta blockaavan eli aliohjelmasta palataan vasta kun siirto on suoritettu loppuuncl_int cl::CommandQueue::enqueueReadBuffer(
const Buffer& buffer,
cl_bool blocking_read,
::size_t offset,
::size_t size,
const void * ptr,
const VECTOR_CLASS<Event> * events = NULL,
Event * event = NULL)
err = queue.enqueueReadBuffer(deviceBuffer, CL_TRUE, 0, N*sizeof(int),
hostBuffer, 0, 0);
if(err != CL_SUCCESS) {
std::cout << "OpenCL-laite -> Isäntälaite -siirtokäskyn asettaminen " \
"komentojonoon epäonnistui." << std::endl;
std::cerr << "OpenCL-virhekoodi: " << err << std::endl;
return 1;
}