CUDA入门

CUDA简介

CUDA是NVIDIA发布的GPU上的并行计算平台和模型, 2006年第一代CUDA发布,截至2018年最新的是9代CUDA.

hello cuda!


__global__ void helloWorld() //__globa__是关键字
{
printf("Hello CUDA!");
}
int main(int argc char* argv[])
{
helloWorld<<<3,2>>>(); //用3个block,每个block2个线程执行helloWorld()
cudaDeviceSynchronize(); //函数执行后必须有这个同步函数
return 0;
}

CUDA中的线程管理

CUDA中线程管理分为三个层次: grid, block, thread, 每一个层次都有x-y-z三个维度.
一组thread组成一个block, 一组block组成一个grid.

threadIdx.x, threadIdx.y, threadIdx.z: thread层三个维度访问方法
blockIdx.x, blockIdx.y, blockIdx.z: block层三个维度访问方法
gridIdx.x, gridIdx.y, gridIdx.z: grid层三个维度访问方法
blockDim.x, blockDim.y, blockDim.z: block每个维度上的长度
gridDim.x,gridDim.y,gridDim.z: grid每个维度上的长度

CUDA这种线程格局和程序中多维数组一致,所以CUDA优化的一个重要思想是: 利用线程index替换数组下标,一个线程处理数组中一个元素,达到并行目的.

CUDA中的内存管理

简单的内存管理用cudaMallocManage和cudaFree即可, 对应CPU下的malloc/free

CUDA中的错误处理

CUDA下用CPU下的debug方式比较困难,所以错误处理反而成为有效的debug手段, 下面是一个处理cuda error的宏.

#define checkCudaErrors( a ) do { \
if (cudaSuccess != (a)) { \
fprintf(stderr, "Cuda runtime error in line %d of file %s \
: %s \n", __LINE__, __FILE__, cudaGetErrorString(cudaGetLastError()) ); \
exit(EXIT_FAILURE); \
} \
} while(0);

CUDA中数据量和线程数不一致的处理方法

以向量加法为例


__global__ void vecAdd(int* a, int* b, int* c, int num)
{
int idx = blockIdx.x * blockDim.x + threadIdx.x;
c[idx] = a[idx] + b[idx];
return;
}
void initCudaVec(int* a, int num, int firstVal)
{
int* mem = (int*)malloc(sizeof(int)*num);
for(int k = 0; k < num; k++ ) mem[k] = firstVal + k;
checkCudaErrors( cudaMemcpy(a, mem, sizeof(int)*num,cudaMemcpyHostToDevice) );
free(mem);
}
int main(int argc char* argv[])
{
const int N = 100;
const int blockNum = 20, thread_per_block = 5;
int *a, *b, *c;
int res[N];
checkCudaErrors( cudaMalloc((void**)&a, sizeof(int)*N) );
checkCudaErrors( cudaMalloc((void**)&b, sizeof(int)*N) );
checkCudaErrors( cudaMalloc((void**)&c, sizeof(int)*N) );
initCudaVec(a, N, 0);
initCudavec(b, N, 11);
vecAdd<<<blockNum,thread_per_block>>>();
checkCudaErrors( cudaGetLastError() );
cudaDeviceSynchronize();
checkCudaErrors( cudaMemcopy(res,c,sizeof(int)*num, cudaMemcpyoDeviceToHost) );
for(int k = 0; k < N; k++) printf("%d\t",k);
checkCudaErrors( cudaFree(a) );
checkCudaErrors( cudaFree(b) );
checkCudaErrors( cudaFree(c) );
return 0;
}

线程数大于数据

上面的例子中,线程数20x5, 数据量100, 刚好相等, 如果线程数大于数据量时,为了避免线程访问越界,需要做如下保护:

__global__ void vecAdd(int* a, int* b, int* c, int num)
{
int idx = blockIdx.x * blockDim.x + threadIdx.x;
if(idx < num) //越界保护
c[idx] = a[idx] + b[idx];
return;
}

线程数小于数据

如果线程数小于数据量时,每个线程需要处理多组数据,优化策略是把数据分组,每组的数据量和线程数量一致,修改如下:

__global__ void vecAdd(int* a, int* b, int* c, int num)
{
//默认只有一个grid??
int offset = blockIdx.x * blockDim.x + threadIdx.x;//当前线程索引
int step = gridDim.x * blockDim.x; //线程总数
for(int k = offset; k < num; k += step)
c[idx] = a[idx] + b[idx];
return;
}

CUDA优化思路

  • 用线程index替换数组索引
    上面已经有例子了

  • 用线程index替换循环变量
    把循环变量想象成数组索引,只是这个索引在循环体内不一定是用来索引内存的,不过优化也不涉及循环体

猜你喜欢

转载自blog.csdn.net/z0n1l2/article/details/80617235