OBS.: Não é minha intenção explicar os detalhes pequenos do CUDA nem algumas noções básicas, isso pode ser encontrado com muitos detalhes e bem explicado no CUDA programming guide
O CUDA, por ser uma tecnologia de programação de multiprocessadores paralelos em placas de vídeo da NVIDIA tem como característica utilizar muitas threads para poder realizar a computação desejada. Quando fala-se de muitas threads, estamos falando na casa dos milhares. Isto para obter uma eficiência boa com a placa de vídeo. Há outros problemas, como maximizar a quantidade de cálculos efetuados dentro da GPU para que "valha a pena" efetuar esta computação na GPU, no entanto, isto está além do escopo deste artigo.
Bem, mãos à obra.
Inicialmente a primeira coisa a se fazer é criar um arquivo chamado 'adicao_vetores.cu'.
Abre-se então um editor de texto de preferência e começamos a editar o arquivo:
Iniciando como um programa C comum (com algumas coisas básicas do CUDA já incluidas)
#include
#include
int main(int argc, char ** argv) {
CUT_DEVICE_INIT();
return 0;
}
Precisamos de 4 vetores: a, b, resultados_cpu, resultados_gpu (já com valores dentro destes vetores):
int main(int argc, char ** argv) {
CUT_DEVICE_INIT();
int n = 10; //numero de elementos do vetor
float * a = new float[n];
float * b = new float[n];
float * resultados_cpu = new float[n];
float * resultados_gpu = new float[n];
//Dados de entrada
for (int i = 0; i <>
for (int i = 0; i <>
Ótimo, agora temos uma soma de vetores implementada em CPU. No entanto, agora temos que adicionar um método para adicionar estes vetores utilizando a GPU:
__global__ void adicionar_vetor(float * a, float * b, float * resultado, int n) {
unsigned int id = threadIdx.x + blockDim.x * blockIdx.x;
if (id <>
- __global__: Esta variável especifica que este é um método de entrada de invocação de computação na GPU. Isto significa que esta função será executada na GPU.
- threadIdx, blockIdx, blockDimx: estas são as variáveis que nos dizem como identificar o ID de uma thread, tornando possível saber onde escrever o resultado da computação
float * d_a;
float * d_b;
float * d_c;
cudaMalloc((void**)&d_a, sizeof(float) * n);
cudaMalloc((void**)&d_b, sizeof(float) * n);
cudaMalloc((void**)&d_c, sizeof(float) * n);
cudaMemcpy(d_a, a, sizeof(float) * n, cudaMemcpyHostToDevice);
cudaMemcpy(d_b, b, sizeof(float) * n, cudaMemcpyHostToDevice);
Note as novas variáveis ponteiro prefixadas com 'd_'. Fiz isto para identificar variáveis que irão ficar no 'device', ou seja, na GPU. Isto não é necessário, está apenas para deixar o código mais claro.
É alocado espaço para estas variáveis na GPU através do comando 'cudaMalloc', que funciona muito similar ao comando 'malloc'.
O método 'cudaMemcpy' possui a seguinte assinatura (neste exemplo):
void cudaMemcpy(endereco destino, endereco fonte, tamanho dados, tipo de transferencia)
- Endereço destino: como queremos copiar esse endereço para a GPU, usamos o nosso ponteiro novo prefixado com 'd_'
- Endereço fonte: onde estão os dados do vetor
- Tamanho dados: quantidade de dados a serem copiados
- Tipo de transferencia: indica a direção que os dados serão transferidos, pode ser (cudaMemcpyHostToDevice, cudaMemcpyDeviceToDevice,cudaMemcpyDeviceToHost,cudaMemcpyHostToHost)
adicionar_vetor<<<512,>>>(d_a,d_b,d_c,n);
Note quais ponteiros foram utilizados, isto é muito importante. Os dois números 512,512 significam o número de blocos e threads por bloco, respectivamente. Escolher estes números é algo relativo ao problema, apenas escolhi 512 e 512 porque este código poderá somar um vetor de até 512*512=262144 elementos.
Está quase pronto, apenas necessitamos pegar os resultados de volta para a CPU para efetuar uma comparação:
//Obtem os resultados de volta
cudaMemcpy(resultados_gpu, d_c, sizeof(float) * n, cudaMemcpyDeviceToHost);
//verifica se o resultado está ok
for (int j = 0; j < esperado =" a[j]" i="%i">/lib" -I"