Este repositório contém materiais de pesquisa, exemplos de código e recursos de documentação da biblioteca OpenCL (Open Computing Language).
Todos os documentos de referências usados nesse repositório podem ser encontrados aqui.
Para trabalhar com os materiais deste repositório é necessário ter:
- Compreensão básica dos conceitos de programação paralela.
- Drivers da placa de vídeo atualizados, a maioria das fabricantes oferecem suporte ao OpenCL através dos drivers de vídeo, consulte NVIDIA ou AMD.
- (Windows) Microsoft Visual Studio com a carga de trabalho "Desenvolvimento para Desktop com C++".
- (Linux) Visual Studio Code e CMake.
- (Opcional) A Intel fornece suporte ao OpenCL para CPUs baseadas em x86/x64 através do Intel OpenCL Runtime, disponível para Windows e Linux, é uma alternativa caso queira executar código OpenCL na CPU ou caso sua GPU não tenha suporte adequado ao OpenCL.
/examples- Exemplos de código demonstrando conceitos e recursos do OpenCL/linux- Projeto com VS Code + CMake/windows- Projeto com Visual Studio/images- Imagens usadas neste README
Os códigos de exemplo estão implementados usando a api de abstração do OpenCL usando classes e recursos modernos do C++, consulte OpenCL C++ Wrapper API para mais detalhes.
Clone este repositório:
git clone https://github.com/Afonso017/OpenCL.git
cd OpenCLO OpenCL SDK já está instalado no diretório /windows/OpenCL/OpenCL_SDK/bin e o projeto do Visual Studio já está configurado para usar o OpenCL.
Após clonar o repositório, entre na pasta do OpenCL SDK e execute o comando clinfo para saber se o OpenCL está configurado corretamente no seu sistema:
cd windows\OpenCL\OpenCL_SDK\bin
clinfoExemplo de saída do comando clinfo:
O clinfo lista e exibe informações sobre plataformas e dispositivos disponíveis, normalmente uma única plataforma e um único dispositivo do tipo GPU em computadores pessoais, e se você instalou o Intel OpenCL Runtime, sua CPU também será listada.
Para executar a ferramenta clinfo a qualquer momento, adicione a pasta bin ao PATH do sistema.
Caso a ferramenta não encontre seu dispositivo, provavelmente é algum problema com o driver de vídeo, tente reinstalá-lo.
Abrindo o arquivo da solução \windows\OpenCL\OpenCL.sln é possível ver os arquivos de exemplo do diretório \examples pois a estrutura do repositório foi construída visando a compatibilidade entre os sistemas Windows e Linux usando o mesmo código-fonte.
Não é possível executar um projeto no Visual Studio com mais de um arquivo com o método main() definido, então cada exemplo possui uma macro MAIN que permite habilitar o código para ser executado.
Descomente a linha //#define MAIN do arquivo hello.cpp e rode o projeto clicando no botão verde ou com o atalho Ctrl + F5 para executar seu primeiro "Hello, World!" no OpenCL.
Para executar outros exemplos, comente a linha #define MAIN do arquivo atual e descomente a do outro arquivo.
É preciso copiar os arquivos .cl para o diretório do projeto para que o programa consiga encontrá-los, eles estão configurados para serem copiados automaticamente durante o build do projeto.
É possível configurar clicando com o botão direito no arquivo .cl, indo em Properties e definindo Item Type para Copy File, depois clique em Aplicar.
Vai aparecer outra propriedade para configurar o caminho do arquivo e o diretório de destino. Defina Destination Directories para $(ProjectDir) e Content Root Folder para ../../examples que é a pasta onde estão os arquivos .cl.
Segundo a documentação oficial da Khronos Group, OpenCL é um padrão aberto para programação paralela de alto desempenho para sistemas heterogêneos, incluindo CPUs multicore, GPUs many-core, DSPs e outros processadores.
O OpenCL torna possível a escrita de código multi-plataforma para tais dispositivos, sem a necessidade do uso de linguagens e ferramentas específicas de fabricante.
Com foco no paralelismo e programação de alto desempenho, o OpenCL baseia-se na escrita de funções que são executadas em múltiplas instâncias simultâneas, chamadas de Kernel.
Este repositório visa introduzir conceitos-chave e explorar a arquitetura definida no padrão OpenCL.
Fonte: Programação em OpenCL: Uma introdução prática
O OpenCL funciona em várias camadas de abstração, a camada de mais alto nível é a camada de aplicação, onde é executado o código do host que envia tarefas para o dispositivo através de uma fila de comandos.
Enquanto a camada de aplicação pode ser escrita tanto em C como C++, o código dos kernels é escrito em uma variante da linguagem ISO C99. Esses kernels são compilados em tempo de execução e podem ser definidos em um arquivo separado ou simplesmente em uma string dentro do programa.
O OpenCL é usado principalmente em áreas que demandam alto desempenho. Na computação científica, ele acelera simulações em física, química e modelagem climática, enquanto no processamento de imagens, otimiza algoritmos de visão computacional e análise médica. Também é utilizado em machine learning para inferência em redes neurais, em jogos para física e pós-processamento gráfico, e em finanças para trading de alta frequência. Além disso, o OpenCL é relevante em telecomunicações (processamento de sinais 5G), sistemas embarcados (IoT e FPGAs) e até na mineração de criptomoedas, como no Ethereum.
Uma das principais vantagens do OpenCL em relação ao CUDA é sua portabilidade multiplataforma, funcionando em hardware da AMD, Intel, ARM e outros, sem depender de fabricante específico. Isso o torna ideal para projetos que exigem flexibilidade ou operam em ambientes heterogêneos, embora o CUDA ainda domine em aplicações de deep learning dedicadas a GPUs NVIDIA. Sua capacidade de aproveitar diferentes tipos de processadores faz do OpenCL uma ferramenta valiosa para otimização de desempenho em diversos cenários.
Fonte: Programação em OpenCL: Uma introdução prática
O modelo de plataforma OpenCL é baseado em uma arquitetura hierárquica que organiza os componentes de hardware e software em diferentes níveis. Ele é composto pelos seguintes elementos principais:
-
Host/Hospedeiro: O host é o dispositivo principal que controla a execução dos programas OpenCL. Geralmente, é a CPU do sistema, responsável por enviar comandos e gerenciar os dispositivos.
-
Plataformas: As plataformas representam os fornecedores dos componentes de hardware que suportam implementações do OpenCL. Intel, AMD e NVIDIA, por exemplo.
-
Dispositivos: São os componentes de hardware físico que executam os
kernelsOpenCL. Podem incluir GPUs, CPUs adicionais, FPGAs, DSPs ou outros processadores. -
Unidades de Computação (Compute Units): Cada dispositivo é dividido em unidades de computação, que são responsáveis por executar as tarefas em paralelo.
-
Elementos de Processamento (Processing Elements): Dentro de cada unidade de computação, existem elementos de processamento que executam as instruções individuais de um kernel.
O modelo de plataforma também define como os programas OpenCL interagem com esses componentes. Ele utiliza um modelo de execução baseado em filas de comandos, onde o host envia tarefas para os dispositivos por meio de uma API padronizada. Essas tarefas incluem a execução de kernels, transferência de dados entre o host e os dispositivos, e sincronização.
Essa arquitetura permite que o OpenCL seja altamente flexível e escalável, suportando uma ampla variedade de dispositivos heterogêneos em um mesmo programa.
Veja o exemplo /examples/infrastructure.cpp que mostra como selecionar um ou mais dispositivos e plataformas do sistema, bem como exibir algumas informações.
Fonte: Khronos OpenCL-Guide Github Repository
O Runtime OpenCL define como o código do kernel é distribuído pela arquitetura do dispositivo, ele permite que o programador controle a organização dos kernels em work-groups.
Ele também gerencia para quais Elementos de Processamento (Processing Elements) cada instância do kernel será mapeado.
As instâncias do kernel são chamadas de work-items, cada work-item é executado por um Elemento de Processamento distinto, e um grupo de work-items, chamados de work-group, é mapeado à uma Unidade de Computação (Computing Unit) que possui uma memória local compartilhada entre os work-items de um mesmo work-group.
O programador define variáveis OpenCL como globalSize e localSize do tipo NDRange, o globalSize é a quantidade total de instâncias do kernel (work-items) que serão executados, e o localSize é o tamanho de um work-group, ou seja, quantos work-items formam um work-group.
É possível definir globalSize e localSize em até 3 dimensões, o exemplo da imagem usa NDRanges bidimensionais, onde o localSize é 8x8 = 64 work-items por
work-group e o globalSize é 4x64 = 256 work-items no total.
Cada um dos work-groups será mapeado a uma Unidade de Computação e cada um dos work-items será mapeado a um Elemento de Processamento de sua Unidade de Computação correspondente.
Veja o exemplo /examples/kernel.cpp que mostra como a execução de um kernel se comporta com valores diferentes para globalSize e localSize.
Fonte: Khronos OpenCL-Guide Github Repository
O OpenCL possui hierarquias de memória, relativas às memórias físicas do sistema e dos dispositivos.
A memória do host, normalmente, é a memória RAM. A memória constante/global pode ser acessada por qualquer work-item no dispositivo. Cada Unidade de Computação possui uma memória local, compartilhada entre work-items de um mesmo work-group. Cada Elemento de Processamento possui uma memória privada para um work-item correspondente.
O gerenciamento de memória e sincronização entre elas é explícito, o programador precisa criar buffers de comunicação entre CPU e GPU (ou qualquer outro dispositivo) para transferir os dados necessários entre as memórias.
Exemplo de kernel que realiza a soma de dois vetores A e B para um vetor resultante C:
__kernel void vector_sum(
__global const int* A, // ponteiro para o primeiro vetor
__global const int* B, // ponteiro para o segundo vetor
__global int* C // ponteiro para o vetor C
) {
int id = get_global_id(0); // ID do thread atual, que é o índice do vetor
C[id] = A[id] + B[id];
}Perceba que esse kernel realiza a soma apenas para um elemento do array C, mas se definirmos globalSize como o tamanho do array, cada soma será executada, em paralelo, em um work-item específico.
O método get_global_id(0) retorna o índice do work-item correspondente na dimensão 0, podemos chamar esse método apenas com os números 0, 1 e 2, representando as 3 dimensões possíveis de um NDRange.
Veja o exemplo /examples/vector_sum.cpp que mostra como criar buffers de memória e passar argumentos para o kernel.
- Especificações Oficiais do OpenCL
- Páginas de Referência do OpenCL
- Introduction to OpenCL Programming (C/C++)
- Tutorial: Simple start with OpenCL and C++
Este repositório de pesquisa está licenciado sob a Licença MIT.



