Explique a primeira rotina de CUDA em detalhes
I. Visão geral
- O propósito de usar a programação CUDA: Quando os métodos de aceleração comuns ( instruções SIMD , C++ multi-threading , OpenMP , etc.) .
- Por exemplo: algoritmo de correspondência estéreo, treinamento e teste de aprendizagem profunda, reconstrução 3D, etc.
- Requisitos de hardware: Você pode verificar a placa gráfica que suporta CUDA e o poder de computação da placa gráfica no site oficial .
2. Instalação do CDUA
O processo de instalação é relativamente simples, simplesmente dividido em três etapas:
- 1. Prepare o instalador VS e CUDA baixado do site oficial (eu uso 10.2, existe uma versão superior agora, você pode experimentar). Durante o processo de instalação, ele detectará automaticamente se uma das versões de suporte do VS foi instalada na máquina. Se a versão do VS não corresponder à versão do Cuda, a instalação não poderá prosseguir. Além disso, se o computador tiver antivírus 360 instalado (é melhor desligá-lo diretamente), haverá avisos contínuos de suspeita de modificação de vírus durante o processo de instalação e todas as operações devem ser permitidas, caso contrário, a instalação não será possível.
- 2. Após a conclusão da instalação, você pode abrir a janela de comando, digitar
path
e verificar se existem variáveis de ambiente correspondentes , conforme mostrado na figura abaixo.
Caso contrário, você mesmo pode adicionar variáveis de ambiente . Geralmente, existem, porque são adicionados por padrão durante a instalação. Usenvcc -V
o comando para visualizar as informações de instalação CUDA correspondentes.
- 3. Abra o VS e você descobrirá que existe uma opção adicional de NVIDA. Após selecioná-la, você pode criar um novo projeto CUDA.
- 4, Referência: https://blog.csdn.net/HaleyDong/article/details/86093520
3. Descrição simples da estrutura
- Veja a seguir um processo simples de fluxo de dados de host (CPU) e dispositivo (pode ser chamado de CUDA ou GPU).
- Grid é a camada mais externa, chamada grid, geralmente tridimensional, que
gridDim.x,gridDim.y, gridDim.z
representa o tamanho de cada dimensão do grid. - Block representa um bloco de thread na grade, geralmente tridimensional, que
blockDim.x,blockDim.y,blockDim.z
representa o tamanho de cada dimensão do bloco de thread;blockIdx.x,blockIdx.y,blockIdx.z
representa o índice do bloco de thread na grade. - O mais interno é o encadeamento realmente usado, que também é uma distribuição tridimensional, que
threadIdx.x,threadIdx.y,threadIdx.z
indica o valor do índice de cada encadeamento nas direções x, y e z no bloco de encadeamento. - A distribuição específica de threads e blocos de threads é mostrada na figura a seguir:
- Nota: Threads em blocos diferentes não podem afetar uns aos outros! Eles estão separados fisicamente! Threads em um bloco de thread podem interagir através da memória compartilhada . Lembre-se primeiro desses dois pontos e os discutiremos em detalhes mais tarde.
4. Exemplo padrão
- Depois de usar o VS para criar um novo projeto CUDA, um arquivo kernel.cu será exibido por padrão. A seguir, uma anotação detalhada do arquivo:
#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#include <stdio.h>
cudaError_t addWithCuda(int *c, const int *a, const int *b, unsigned int size);
//修饰符“__global__”,这个修饰符告诉编译器,被修饰的函数应该编译为在GPU而不是在CPU上运行,
__global__ void addKernel(int *c, const int *a, const int *b)
{
//threadIdx.x,表示的是thread在x方向上的索引号
int i = threadIdx.x;
c[i] = a[i] + b[i];
}
int main()
{
const int arraySize = 5;
const int a[arraySize] = {
1, 2, 3, 4, 5 };
const int b[arraySize] = {
10, 20, 30, 40, 50 };
int c[arraySize] = {
0 };
//调用GPU运算的入口函数,返回类型是cudaError_t
cudaError_t cudaStatus = addWithCuda(c, a, b, arraySize);
if (cudaStatus != cudaSuccess) {
fprintf(stderr, "addWithCuda failed!");
return 1;
}
printf("{1,2,3,4,5} + {10,20,30,40,50} = {%d,%d,%d,%d,%d}\n",
c[0], c[1], c[2], c[3], c[4]);
//函数用于释放所有申请的显存空间和重置设备状态;
cudaStatus = cudaDeviceReset();
if (cudaStatus != cudaSuccess) {
fprintf(stderr, "cudaDeviceReset failed!");
return 1;
}
return 0;
}
// Helper function for using CUDA to add vectors in parallel.
cudaError_t addWithCuda(int *c, const int *a, const int *b, unsigned int size)
{
int *dev_a = 0;
int *dev_b = 0;
int *dev_c = 0;
cudaError_t cudaStatus;
// Choose which GPU to run on, change this on a multi-GPU system.
// 初始化设备上的GPU,并选择ID为0的GPU执行程序
cudaStatus = cudaSetDevice(0);
if (cudaStatus != cudaSuccess) {
fprintf(stderr, "cudaSetDevice failed! Do you have a CUDA-capable GPU installed?");
goto Error;
}
// Allocate GPU buffers for three vectors (two input, one output).
// 为device(GPU)中的求和数组c分配内存
cudaStatus = cudaMalloc((void**)&dev_c, size * sizeof(int));
if (cudaStatus != cudaSuccess) {
fprintf(stderr, "cudaMalloc failed!");
goto Error;
}
// 为device(GPU)中的数组a分配内存
cudaStatus = cudaMalloc((void**)&dev_a, size * sizeof(int));
if (cudaStatus != cudaSuccess) {
fprintf(stderr, "cudaMalloc failed!");
goto Error;
}
// 为device(GPU)中的数组b分配内存
cudaStatus = cudaMalloc((void**)&dev_b, size * sizeof(int));
if (cudaStatus != cudaSuccess) {
fprintf(stderr, "cudaMalloc failed!");
goto Error;
}
// Copy input vectors from host memory to GPU buffers.
// 将CPU中的数组a数据拷贝到GPU
cudaStatus = cudaMemcpy(dev_a, a, size * sizeof(int), cudaMemcpyHostToDevice);
if (cudaStatus != cudaSuccess) {
fprintf(stderr, "cudaMemcpy failed!");
goto Error;
}
// 将CPU中的数组b数据拷贝到GPU
cudaStatus = cudaMemcpy(dev_b, b, size * sizeof(int), cudaMemcpyHostToDevice);
if (cudaStatus != cudaSuccess) {
fprintf(stderr, "cudaMemcpy failed!");
goto Error;
}
// Launch a kernel on the GPU with one thread for each element.
// “<<<>>>”表示运行时配置符号,在本程序中的定义是<<<1,size>>>,表示分配了一个线程块(Block),每个线程块有分配了size个线程
// 这种设置默认线程块和线程的维度为1,即:blockIdx.x=0,threadId.x的范围为[0,size)
// 一共开arraySize个线程,每个线程执行一组数据的加法。
addKernel<<<1, size>>>(dev_c, dev_a, dev_b);
// Check for any errors launching the kernel 函数用于返回最新的一个运行时调用错误,对于任何CUDA错误,都可以通过函数
cudaStatus = cudaGetLastError();
if (cudaStatus != cudaSuccess) {
fprintf(stderr, "addKernel launch failed: %s\n", cudaGetErrorString(cudaStatus)); //函数来获取错误的详细信息。
goto Error;
}
// cudaDeviceSynchronize waits for the kernel to finish, and returns
// any errors encountered during the launch. 函数提供了一个阻塞,用于等待所有的线程都执行完各自的计算任务,然后继续往下执行。
cudaStatus = cudaDeviceSynchronize();
if (cudaStatus != cudaSuccess) {
fprintf(stderr, "cudaDeviceSynchronize returned error code %d after launching addKernel!\n", cudaStatus);
goto Error;
}
// Copy output vector from GPU buffer to host memory. 函数用于主机内存和设备显存以及主机与主机之间,设备与设备之间相互拷贝数据
cudaStatus = cudaMemcpy(c, dev_c, size * sizeof(int), cudaMemcpyDeviceToHost);
if (cudaStatus != cudaSuccess) {
fprintf(stderr, "cudaMemcpy failed!");
goto Error;
}
Error:
cudaFree(dev_c); //函数用于释放申请的显存空间。
cudaFree(dev_a);
cudaFree(dev_b);
return cudaStatus;
}
- Os comentários acima já são muito detalhados, então não vou explicar seus significados específicos. Os resultados reais da execução são os seguintes:
- Um applet hello_world:
#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#include <stdio.h>
__global__ void hello_world(void)
{
printf("GPU: Hello world!\n");
}
int main(int argc, char **argv)
{
printf("CPU: Hello world!\n");
hello_world << <1, 10 >> >();
cudaDeviceReset();//if no this line ,it can not output hello world from gpu
return 0;
}
- A saída resultante:
5. Referência
Blog de Tan Sheng: