Introdução ao OpenCL

menotti
14.6K views

Open Source Your Knowledge, Become a Contributor

Technology knowledge has to be shared and made accessible for free. Join the movement.

Create Content

Olá mundo

Como de costume em qualquer tutorial de programação, vamos começar por este olá mundo adaptado de um livro gratuito disponível na internet. O programa não faz nenhum cálculo, apenas retorna a mensagem "Hello, World!" gerada no kernel. Como a entrada e saída padrão do sistema não podem ser usadas a partir do kernel, ele apenas vai preencher a cadeia de caracteres e enviá-la ou programa principal que irá imprimi-la na console.

Incluindo o arquivo de cabeçalho OpenCL

Toda aplicação OpenCL deve incluir o arquivo de cabeçalho CL/cl.h:

#include <CL/cl.h>

Mas antes disso, nós estamos definindo CL_USE_DEPRECATED_OPENCL_1_2_APIS para usar a versão 1.2 da API sem gerar warnings durante a compilação:

#define CL_USE_DEPRECATED_OPENCL_1_2_APIS

Esta versão não é a mais recente, mas é suportada pela maioria dos dispositivos OpenCL.

Configuração no host

No main, nós definimos uma série de variáveis cujos tipos, definidos na biblioteca OpenCL, possuem o prefixo cl_ que explicaremos a seguir:

cl_device_id device_id = NULL;
cl_context context = NULL;
cl_command_queue command_queue = NULL;
cl_mem memobj = NULL;
cl_program program = NULL;
cl_kernel kernel = NULL;
cl_platform_id platform_id = NULL;
cl_uint ret_num_devices;
cl_uint ret_num_platforms;
cl_int ret;

Configurando uma fila (Queue) OpenCL

Para enviar nossas tarefas para serem agendadas e executadas no dispositivo de destino, precisamos usar uma fila (Queue) OpenCL associada a um dispositivo. Primeiro pegamos o primeiro dispositivo da primeira plataforma disponível:

/* Get Platform and Device Info */
ret = clGetPlatformIDs(1, &platform_id, &ret_num_platforms);
ret = clGetDeviceIDs(platform_id, CL_DEVICE_TYPE_DEFAULT, 1, &device_id, &ret_num_devices);

Depois criamos um contexto de execução e uma fila associada e este contexto:

/* Create OpenCL context */
context = clCreateContext(NULL, 1, &device_id, NULL, NULL, &ret);

/* Create Command Queue */
command_queue = clCreateCommandQueue(context, device_id, 0, &ret);

É possível criar contextos com vários dispositivos, mas cada fila está associada e um único dispositivo.

Configurar armazenamento do dispositivo

Na maioria dos sistemas, o host e o dispositivo não compartilham memória física. Por exemplo, a CPU pode usar RAM e a GPU pode usar sua própria VRAM interna. O OpenCL precisa saber quais dados serão compartilhados entre o host e os dispositivos.

Para esse fim, existem buffers OpenCL. A função a seguir cria um buffer que pode ser lido e escrito (CL_MEM_READ_WRITE).

/* Create Memory Buffer */
memobj = clCreateBuffer(context, CL_MEM_READ_WRITE,MEM_SIZE * sizeof(char), NULL, &ret);

Compilando o kernel

Os próximos passos são criar (clCreateProgramWithSource) e compilar (clBuildProgram) um programa que resultará em uma bibliteca dinâmica para o dispositivo alvo. Nosso único kernel está armazenado em um arquivo externo (hello.cl), mas ele poderia conter mais de um, então precisamos indicar o seu nome hello como argumento na função clCreateKernel:

/* Create Kernel Program from the source */
program = clCreateProgramWithSource(context, 1, (const char **)&source_str,
(const size_t *)&source_size, &ret);

/* Build Kernel Program */
ret = clBuildProgram(program, 1, &device_id, NULL, NULL, NULL);

/* Create OpenCL Kernel */
kernel = clCreateKernel(program, "hello", &ret);

Executando o kernel

Agora basta atribuir o argumento que será usado no kernel e enfileirar sua execução na fila do dispositivo:

/* Set OpenCL Kernel Parameters */
ret = clSetKernelArg(kernel, 0, sizeof(cl_mem), (void *)&memobj);

/* Execute OpenCL Kernel */
ret = clEnqueueTask(command_queue, kernel, 0, NULL,NULL);

Nosso kernel não recebe nenhum dado do host, mas quando for necessário, é preciso enviar os dados antes da execução usando clEnqueueWriteBuffer.

Lendo e exibindo o resultado

Após a execução, enfileiramos a transferência dos dados do dispositivo para host com a função clEnqueueReadBuffer que tem como destino a string declarada no host e a exibimos:

/* Copy results from the memory buffer */
ret = clEnqueueReadBuffer(command_queue, memobj, CL_TRUE, 0,
MEM_SIZE * sizeof(char),string, 0, NULL, NULL);

/* Display Result */
puts(string);

Limpando

Como estamos usando a versão C da API, precisamos desalocar os recursos:

/* Finalization */
ret = clFlush(command_queue);
ret = clFinish(command_queue);
ret = clReleaseKernel(kernel);
ret = clReleaseProgram(program);
ret = clReleaseMemObject(memobj);
ret = clReleaseCommandQueue(command_queue);
ret = clReleaseContext(context);

free(source_str);

Isso pode não ser necessário em linguagens de programação mais modernas como C++, Python e Java.

Vamos executar!

Hello World - OpenCL
Open Source Your Knowledge: become a Contributor and help others learn. Create New Content