__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 tekee aliohjelmasta ytimen; muuten kyseessä on normaali OpenCL-laitteen puoleinen aliohjelma, jota voidaan kutsua ytimestä tai toisesta OpenCL-laitteen puoleisesta aliohjelmastaint get_global_id(int)
-aliohjelma palauttaa säikeen globaalin indeksinumeron__global__ void add_one(int *buffer, int n) {
const int global_id = blockIdx.x * blockDim.x + threadIdx.x;
if(global_id < n)
buffer[global_id]++;
}
__global__
-avainsana tekee aliohjelmasta ytimen__device__
-avainsana tekisi aliohjelmasta puhtaan CUDA-aliohjelman, jota voidaan kutsua pelkästään ytimestä tai toisesta CUDA-aliohjelmastaadd_one
suorittamat säikeet voidat seurata kahta eri suorituspolkua:buff
if(global_id < n)
-ehtolause takaa sen, että ylimääräiset ytimet eivät aiheuta puskurin ylivuotoa:Aliohjelma | Selitys |
---|---|
uint get_work_dim () | Globaalin avaruuden dimensio |
size_t get_global_size (uint D) | Globaalin avaruuden koko / säikeiden kokonaismäärä |
size_t get_global_id (uint D) | Säikeen globaali indeksinumero |
size_t get_local_size (uint D) | Lokaalin avaruuden koko / Säieryhmän koko |
size_t get_local_id (uint D) | Säikeen lokaali indeksinumero |
size_t get_num_groups (uint D) | Säieryhmien määrä |
size_t get_group_id (uint D) | Säieryhmän indeksinumero |
const int local_id = get_local_id(0);
const int local_size = get_local_size(0);
const int idx = get_group_id(0);
const int jdx = get_group_id(1);
for(int i = local_id; i < N; i += local_size)
...
// Säikeet laskevat yhdessä muuttujaan value jotain...
// Vain säieryhmän ensimmäinen säie tallentaa lopullisen tuloksen
if(local_id == 0)
A[idx*N+jdx] = value;
Muuttuja | Selitys |
---|---|
dim3 gridDim | Säieryhmien määrä |
dim3 blockDim | Säieryhmän koko |
uint3 blockIdx | Säieryhmän indeksinumero |
uint3 threadIdx | Säikeen lokaali indeksinumero |
const int local_id = threadIdx.x;
const int local_size = blockDim.x;
const int idx = blockIdx.x;
const int jdx = blockIdx.y;
for(int i = local_id; i < N; i += local_size)
...
// Säikeet laskevat yhdessä muuttujaan value jotain...
// Vain säieryhmän ensimmäinen säie tallentaa lopullisen tuloksen
if(local_id == 0)
A[idx*N+jdx] = value;
cl::Buffer
-luokan avulla:cl::Buffer::Buffer(
const Context& context,
cl_mem_flags flags,
::size_t size,
void * host_ptr = NULL,
cl_int * err = NULL)
flags
) CL_MEM_READ_WRITE
varaa read-write muistialueenhost_ptr != 0
ja lippu CL_MEM_COPY_HOST_PTR
on asetettu, host_ptr
-osoittimen osoittava data siirretään GPU:n muistiin automaattisestiCL_MEM_USE_HOST_PTR
, CL_MEM_ALLOC_HOST_PTR
, CL_MEM_HOST_WRITE_ONLY
, CL_MEM_HOST_READ_ONLY
ja CL_MEM_HOST_NO_ACCESS
cudaMalloc
-aliohjelmalla:cudaError_t cudaMalloc(
void ** devPtr,
size_t size);
cudaMallocPitch
-aliohjelmalla:cudaError_t cudaMallocPitch(
void **devPtr,
size_t *pitch,
size_t M,
size_t N);
A
elementit saadaan käyttöön seuraavalla tavalla:int elem1 = *((int*)((char*)A + 3 * pitch) + 5); // "=" A[3][5]
int *line = (int*)((char*)A + 7 * pitch); // 7. rivi
int elem2 = line[14]; // "=" A[7][14]
cudaMalloc3D
-aliohjelmallacudaFree
-aliohjelmallacudaError_t cudaFree(void *devPtr);
cudaMallocHost
ja cudaFreeHost
aliohjelmat ns. page-locked / pinned muistin varaamiseen isäntälaitteen puolella. Tämä tekee muistisiirroista nopeampia, mutta suurien taulukoiden vaaraaminen saattaa epäonnistua tai vaikuttaa negatiivisella tavalla laitteiston yleiseen suorituskykyyn.__device__ double devData; // CUDA-laitteelle näkyvä globaali muuttuja
__device__ double* devPointer; // CUDA-laitteelle näkyvä globaali osoitin
double hostData = 6.0;
cudaMemcpyToSymbol(devData, &hostData, sizeof(double));
double* hostPointer;
cudaMalloc(&hostPointer, 256*sizeof(double));
cudaMemcpyToSymbol(devPointer, &hostPointer, sizeof(hostPointer));
__kernel void ydin(...) {
__local float buff[256];
const int local_id = get_local_id(0);
buff[local_id] = local_id;
...
}
ydin.setArg(0, cl::Local(256*sizeof(float));
queue.enqueueNDRangeKernel(ydin, ...);
typedef cl::make_kernel<cl::LocalSpaceArg, ...> createKernel;
typedef std::function<createKernel::type_> KernelType;
KernelType ydin = makeKernel(...);
ydin(EnqueueArgs(...), cl::Local(256*sizeof(float), ...);
__kernel void ydin(__local float *buff, ...) {
const int local_id = get_local_id(0);
buff[local_id] = local_id;
...
}
__global__ void ydin(...) {
__shared__ float buff[256];
const int local_id = threadIdx.x;
buff[local_id] = local_id;
...
}
ydin<<<WG_COUNT, LOCAL_SIZE, 256*sizeof(float)>>>(...);
extern
) muuttujana:__global__ void ydin(...) {
extern __shared__ float buff[];
const int local_id = threadIdx.x;
buff[local_id] = local_id;
...
}
cudaDeviceSetSharedMemConfig
-aliohjelmalla:cudaError_t cudaDeviceSetSharedMemConfig(cudaSharedMemConfig config);
cudaSharedMemBankSizeFourByte
asettaa pankkien leveydeksi 4 tavua (float)cudaSharedMemBankSizeEightByte
asettaa pankkien leveydeksi 8 tavua (double)cl::Buffer
-luokan muodostinfunktiolle CL_MEM_READ_ONLY
-lipun__constant
-avainsanalla argumentin yhteydessä:__kernel void ydin(__constant float *buff, ...) {
const int local_id = get_local_id(0);
float a = buff[local_id]; // OK
buff[local_id] = 7.0; // Virhe
}
__constant__ float buff[256];
__global__ void ydin(...) {
const int local_id = threadIdx.x;
float a = buff[local_id]; // OK
buff[local_id] = 7.0; // Virhe
}
buff
-taulukon symbolina, johon voidaan siirtää dataa cudaMemcpyToSymbol
-aliohjelmalla:cudaError_t cudaMemcpyToSymbol(
const char *symbol,
const void *src,
size_t count,
size_t offset = 0,
enum cudaMemcpyKind kind = cudaMemcpyHostToDevice);
__constant__ float buff[256];
float data[256];
// Täytetään data-taulukko tässä välissä
cudaMemcpyToSymbol (buff, data, 256*sizeof(float));
cudaMemcpyToSymbolAsync
-aliohjelmaacl::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)
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)
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)
cl::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)
setArg
-jäsenfunktiolla:template <typename T>
cl_int cl::Kernel::setArg(cl_uint index, T value)
enqueueNDRangeKernel
-jäsenfunktiolla:cl_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)
global
-argumentilla ja säieryhmän koko vastaavasti local
-argumentillatypedef cl::make_kernel<...> createKernel;
<>
-sulkujen sisällecreateKernel
-aliohjelman (itseasiassa kyseessä on olion) avulla voidaan luoda ydin-objekteja, joiden argumenttilista vastaa <>
-sulkujen sisään annettua listaatypedef std::function<createKernel::type_> KernelType;
KernelType
-tyyppi voi viitata ydin-objektiin, jonka argumenttilista vastaa createKernel
-aliohjelman määrittelyn yhteydessä annettua argumenttilistaacreateKernel
-aliohjelmalla:KernelType ydin = createKernel(program, "ytimen_nimi");
kernel(
cl::EnqueueArgs(
queue,
cl::NDRange(GLOBAL_SIZE_0, GLOBAL_SIZE_1, GLOBAL_SIZE_2),
cl::NDRange(LOCAL_SIZE_0, LOCAL_SIZE_1, LOCAL_SIZE_2)),
...);
cl::EnqueueArgs
-olion jälkeentypedef cl::make_kernel<cl::Buffer&, int> createAddOneKernel;
typedef std::function<createAddOneKernel::type_> AddOneKernelType;
AddOneKernelType kernel = createAddOneKernel(program, "add_one");
kernel(
cl::EnqueueArgs(
queue, cl::NDRange((N/256+1)*256), cl::NDRange(256)),
deviceBuffer, N);
cudaSetDevice
vaihtaa käytössä olevan oletuskomentojononcudaStreamCreate
-aliohjelmalla:cudaStream_t queue;
err = cudaStreamCreate(&queue);
cudaStreamDestroy
-aliohjelmalla:err = cudaStreamDestroy(queue);
cudaMemcpy
-aliohjelmalla:cudaError_t cudaMemcpy(
void *destination, // Kohdepuskuri
const void *source, // Lähdepuskuri
size_t count, // Siirron koko tavuina
enum cudaMemcpyKind kind); // Siirron tyyppi
kind
-argumentille ovat:
cudaMemcpyHostToHost
cudaMemcpyHostToDevice
cudaMemcpyDeviceToHost
cudaMemcpyDeviceToDevice
cudaMemcpyAsync
-aliohjelmalla:cudaError_t cudaMemcpyAsync(
void *destination,
const void *source,
size_t count,
enum cudaMemcpyKind kind,
cudaStream_t stream = 0
)
stream
-argumentillacudaMemcpyAsync
-aliohjelmalle annetaan argumenttina isäntälaitteen muistissa sijaitseva puskuri, täytyy tämän puskurin olla ns. page-locked / pinnedcudaMallocHost
-aliohjelmalla:cudaError_t cudaMallocHost(void **ptr, size_t size);
cudaFreeHost
-aliohjelmalla:cudaError_t cudaFreeHost(void *ptr);
kernel<<<gridSize, blockSize, localSize, queue>>>(args)
gridSize
määrittää säieryhmien määrän. Voi olla luku tai dim3
blockSize
määrittää säieryhmän koon. Voi olla luku tai dim3
localSize
määrittää dynaamisesti varattavan lokaalin muistin määrän tavuinaqueue
määrittää käytettävän komentojononargs
määrittää ytimen argumentit// Aloitetaan ei-blockkaava siirto hostBuffer -> deviceBuffer
cudaMemcpyAsync(
deviceBuffer, hostBuffer, N*sizeof(int), cudaMemcpyHostToDevice);
for(int i = 0; i < N; i++)
hostBuffer[i] = i; // hostBufferin i:nes alkio saattaa olla siirretty
// GPU:n muistiin ennen tämän rivin suoritusta tai se
// saatetaan siirtää GPU:n muistiin vasta myöhemmin
cudaMemcpyAsync(
hostBuffer, deviceBuffer, N*sizeof(int), cudaMemcpyDeviceToHost);
for(int i = 0; i < N; i++)
cout << hostBuffer[i] << endl; // Saattaa tulostaa deviceBufferin i:dennen
// alkion tai jotain muuta
queue.finish(); // Aliohjelmasta palataan vasta kun kaikki komentojonossa olleen
// komennot on suoritettu loppuun
cudaStreamSynchronize(queue);
cudaDeviceSynchronize();
cl_int
-tyyppisellä virhemuuttujalla tai poikkeuksien avulla (#define __CL_ENABLE_EXCEPTIONS
)cudaError_t
-tyyppisen virhemuuttujancudaError_t cudaGetLastError(void)
palauttaa edellisen komennon virhekoodin ja resetoi sen arvoon cudaSuccess
cudaError_t cudaPeekAtLastError(void)
palauttaa edellisen komennon virhekoodin// Oikein toimiva muistisiirto, blockkaava / synkroninen
err = cudaMemcpy(
deviceBuffer, hostBuffer, N*sizeof(int), cudaMemcpyHostToDevice);
if(err != cudaSuccess) { ...
// Virheellisesti toimiva ydin, ei-blockkaava / asynkroninen
add_one<<<WGCount, localDim>>>(deviceBuffer, N);
if(cudaGetLastError() != cudaSuccess) {
// Käynnistys meni ok, joten ei virhettä!!!
}
// Oikein toimiva muistisiirto, blockkaava / synkroninen
cudaMemcpy(hostBuffer, deviceBuffer, N*sizeof(int), cudaMemcpyDeviceToHost);
if(err != cudaSuccess) {
// add_one-yimen aiheuttama virhe tulee ilmi täällä!!!
}
cl::CommandQueue::finish()
, cudaStreamSynchronize(cudaStream_t stream)
tai cudaDeviceSynchronize()
palauttavat ajonaikaisista virheestä kertovat virhekoodit:add_one<<<WGCount, localDim>>>(deviceBuffer, N);
if(cudaGetLastError() != cudaSuccess) {
// Ilmoita virheistä, jotka tapahtuivat kun komentoa oltiin asettamassa
// komentojonoon
}
#if DEBUG
err = cudaDeviceSynchronize();
if(err != cudaSuccess) {
// Ilmoita ajonaikaisesta virheestä
}
#endif
-D DEBUG
-lipun kanssa aktivoi yllä esiintyneen debuggauskoodinvoid swap(__local int *buff) {
int local_id = get_local_id(0);
int local_size = get_local_size(0);
// Yritetään vaihtaa taulukon luvut käänteiseen järjestykseen
int x = buff[local_id];
// Osa säikeistä ei ole vielä välttämättä suorittanut edeltävää riviä tässä
// vaiheessa!
buff[local_size-local_id-1] = x;
}
barrier
-esteen avulla:void barrier (cl_mem_fence_flags flags);
flags
-lippu voi olla yhdistelmä seuraavista:
CLK_LOCAL_MEM_FENCE
-lippu takaa, että kaikki lokaaliin muistiin liittyneet operaatiot on suoritettu loppuunCLK_GLOBAL_MEM_FENCE
-lippu takaa, että kaikki globaaliin muistiin liittyeet operaatiot on suoritettu loppuunbarrier
-komento!__syncthreads()
-aliohjelma on sama OpenCL:n kuinbarrier(CLK_LOCAL_MEM_FENCE | CLK_GLOBAL_MEM_FENCE);
__threadfence_block()
, __threadfence()
ja __threadfence_system()
aliohjelmat, joiden lopputulos on heikompi, mutta niistä saattaa olla hyötyä joissakin tapauksissa__syncthreads()
-aliohjelma riittää ihan hyvinvoid swap(__local int *buff) {
int local_id = get_local_id(0);
int local_size = get_local_size(0);
// Jokainen säie lukee luvun taulukosta
int x = buff[local_id];
// Odotetaan, että jokainen säie on lukenut oman lukunsa muuttujaan x
barrier(CLK_LOCAL_MEM_FENCE);
// Kirjoitetaan tulos takaisin taulukkoon
buff[local_size-local_id-1] = x;
}
void swap(__local int *buff, int n) {
int local_id = get_local_id(0);
// Järjestetään vain n ensimmäistä alkiota
if(local_id < n) {
int x = buff[local_id];
// Osa säikeistä ei välttämättä suorita tätä riviä ollenkaan, jonka
// seurauksena if-lohkoon tullet säikeet jäävät ikuisesti odottamaan
// niiden saapumista!
barrier(CLK_LOCAL_MEM_FENCE);
buff[n-local_id-1] = x;
}
}
printf
-aliohjelman käyttöä ytimen sisällä__kernel void add_one(__global int *buffer, int n) {
const int global_id = get_global_id(0);
if(global_id < n)
buffer[global_id]++;
else
printf("Säie %d ei tehnyt mitään.\n", global_id);
}