Programação de GPU com C ++

Programação de GPU com C ++

Visão geral

Neste guia, exploraremos o poder da programação da GPU com C++. Os desenvolvedores podem esperar um desempenho incrível com C ++, e o acesso ao poder fenomenal da GPU com uma linguagem de baixo nível pode produzir parte do cálculo mais rápido atualmente disponível.

Requisitos

Embora qualquer máquina capaz de executar uma versão moderna do Linux possa suportar um compilador C ++, você precisará de uma GPU baseada na NVIDIA para acompanhar este exercício. Se você não tiver uma GPU, pode aumentar uma instância movida a GPU no Amazon Web Services ou outro provedor de nuvem de sua escolha.

Se você escolher uma máquina física, certifique -se de ter os drivers proprietários da NVIDIA instalados. Você pode encontrar instruções para isso aqui: https: // linuxhint.com/install-nvidia-drivers-linux/

Além do motorista, você precisará do CUDA Toolkit. Neste exemplo, usaremos o Ubuntu 16.04 LTS, mas há downloads disponíveis para a maioria das principais distribuições no seguinte URL: https: // desenvolvedor.nvidia.com/cuda-downloads

Para o Ubuntu, você escolheria o .Download baseado em Deb. O arquivo baixado não terá um .Extensão Deb por padrão, então eu recomendo renomeá -lo para ter um .Deb no final. Então, você pode instalar com:

sudo dpkg -i package -name.Deb

Você provavelmente será solicitado a instalar uma tecla GPG e, se assim for, siga as instruções fornecidas para fazê -lo.

Depois de fazer isso, atualize seus repositórios:

Atualização de sudo apt-get
sudo apt -get install cuda -y

Uma vez feito, recomendo a reinicialização para garantir que tudo esteja carregado corretamente.

Os benefícios do desenvolvimento da GPU

As CPUs lidam com muitas entradas e saídas diferentes e contêm uma grande variedade de funções para não apenas lidar com uma ampla variedade de necessidades do programa, mas também para gerenciar configurações variadas de hardware. Eles também lidam.

As GPUs são o oposto - elas contêm muitos processadores individuais focados em funções matemáticas muito simples. Por isso, eles processam tarefas muitas vezes mais rápidas que as CPUs. Especializando -se em funções escalares (uma função que leva uma ou mais entradas, mas retorna apenas uma saída), elas alcançam um desempenho extremo ao custo da extrema especialização.

Código de exemplo

No código de exemplo, adicionamos vetores juntos. Eu adicionei uma versão da CPU e GPU do código para comparação de velocidade.
Exemplo de GPU.cpp Conteúdo abaixo:

#include "cuda_runtime.h "
#incluir
#incluir
#incluir
#incluir
#incluir
typedef std :: crono :: high_resolution_clock relógio;
#Define ITER 65535
// Versão da CPU da função Adicionar vetor
void vector_add_cpu (int *a, int *b, int *c, int n)
int i;
// Adicione os elementos vetoriais A e B ao vetor C
para (i = 0; i < n; ++i)
c [i] = a [i] + b [i];


// Versão GPU da função Adicionar vetor
__Global__ void vector_add_gpu (int *gpu_a, int *gpu_b, int *gpu_c, int n)
int i = threadidx.x;
// não para o loop necessário porque o tempo de execução do CUDA
// vai passar este iter tempos
gpu_c [i] = gpu_a [i] + gpu_b [i];

int main ()
int *a, *b, *c;
int *gpu_a, *gpu_b, *gpu_c;
a = (int *) malloc (iter * sizeof (int));
b = (int *) MALLOC (ITER * sizeof (int));
c = (int *) MALLOC (ITER * sizeof (int));
// precisamos de variáveis ​​acessíveis à GPU,
// SO CUDAMALLOCMANAGADO fornece estes
cudamallocmanaged (& gpu_a, iter * sizeof (int));
cudamallocmanaged (& gpu_b, iter * sizeof (int));
cudamallocmanaged (& gpu_c, iter * sizeof (int));
para (int i = 0; i < ITER; ++i)
a [i] = i;
b [i] = i;
c [i] = i;

// Ligue para a função da CPU e o tempo
Auto cpu_start = relógio :: agora ();
vetor_add_cpu (a, b, c, iter);
Auto cpu_end = relógio :: agora ();
std :: cout << "vector_add_cpu: "
<< std::chrono::duration_cast(cpu_end - cpu_start).contar()
<< " nanoseconds.\n";
// Ligue para a função da GPU e o tempo
// O Brakets de ângulo Triplo é uma extensão de tempo de execução do CUDA que permite
// parâmetros de uma chamada de kernel de Cuda para ser aprovada.
// Neste exemplo, estamos passando um bloco de threads com threads de iter.
Auto gpu_start = relógio :: agora ();
vetor_add_gpu <<<1, ITER>>> (gpu_a, gpu_b, gpu_c, iter);
Cudadevicesynchronize ();
auto gpu_end = relógio :: agora ();
std :: cout << "vector_add_gpu: "
<< std::chrono::duration_cast(gpu_end - gpu_start).contar()
<< " nanoseconds.\n";
// Livre as alocações de memória baseadas em função GPU
cudafree (a);
CudAfree (b);
CUDAFREE (C);
// Livre as alocações de memória baseadas em função CPU
grátis (a);
grátis (b);
grátis (c);
retornar 0;

Makefile Conteúdo abaixo:

Inc = -i/usr/local/cuda/incluir
Nvcc =/usr/local/cuda/bin/nvcc
Nvcc_opt = -std = c ++ 11
todos:
$ (NVCC) $ (NVCC_OPT) GPU-Exemplo.CPP -O GPU -Exemplo
limpar:
-RM -F GPU -Exemplo

Para executar o exemplo, compile:

fazer

Em seguida, execute o programa:

./GPU-Exemplo

Como você pode ver, a versão da CPU (vetor_add_cpu) funciona consideravelmente mais lenta que a versão da GPU (Vector_add_GPU).

Caso contrário, pode ser necessário ajustar o ITER definido no Exemplo de GPU.Cu para um número maior. Isso se deve ao tempo de configuração da GPU por mais tempo do que alguns loops menores com intensidade de CPU. Encontrei 65535 para trabalhar bem na minha máquina, mas sua milhagem pode variar. No entanto, depois de limpar esse limite, a GPU é dramaticamente mais rápida que a CPU.

Conclusão

Espero que você tenha aprendido muito com a nossa introdução à programação da GPU com C++. O exemplo acima não realiza muito, mas os conceitos demonstrados fornecem uma estrutura que você pode usar para incorporar suas idéias para liberar o poder da sua GPU.