一个CUDA程序一般分为两部分
-
Host 运行在CPU端,逻辑判断密集型
-
Device 运行在GPU端,计算密集型
内存分配:无论是CPU端的主存还是GPU端的显存,都必须通过CUDA在主机端进行统一分配
CUDA编程模型:
GPU与CPU交互通过PCIE总线进行互联,CUDA的线程结构是网格-线程块-线程(是CUDA程序的最小执行单元)三级结构。

CUDA函数声明
Kernel函数(入口函数)在CPU上调用在GPU上执行,GPU在执行kernel函数的时候面向线程。

CUDA中的kernel函数是一个可以在CUDA程序中被独立并行执行的一个过程。
CUDA API语法
__global__ void Compute(int *gpudata) { .......}调用方式
Compute<<<m,n>>>(a,b)其中 «< »> 是运算符,用来传递要调用的kernel函数的执行参数,m代表每个Grid中的Block数量,n代表每个Block中的Thread数量, «< »> 也包含每个Block能够分配的额外shared memory大小,隐藏默认为1。 a,b是函数调用的实参。
每个 Grid分为N个Block,每个Block分为M个Thread,则每个线程的序号为 N*M+ThreadID.x。
索引计算方法
int tid = threadIdx.x + blockIdx.x * blockDim.x;-
threadIdx.x表示当前线程所在线程块中某行的索引,
-
blockIdx.x表示线程块索引,
-
blockDim.x表示每个线程块中的线程数量
-
gridDim.x表示当前线程格中线程块的数量
开始学习CUDA编程啦,觉得通过具体的例子来解释CUDA编程的关键点会好很多吧。这是我开始学习的第一个例子,求立方和,以下是完整的代码,具体的解释在各行中有注释,在便于我回忆的同时希望对大家会有帮助。
通过以下形式启动核函数:
add<<<5,1>>>(param1,param2.....) 可以认为运行时将创建核函数的5个副本,并以并行的方式来运行
#include <stdio.h>
#include <assert.h>
#define DATA_SIZE 1048576
int data[DATA_SIZE];
void GenerateNumbers(int *number, int size)
{
for (int i = 0; i < size; i++) {
srand(3);
number[i] = rand() % 10;
}
}
// Simple utility function to check for CUDA runtime errors
void checkCUDAError(const char *msg);
// Part 3 of 5: implement the kernel
__global__ void myFirstKernel(int *num, int *result)
{
int sum = 0;
//计算位于这个索引处的数据,dim3为二维数组,要通过x和y来对应数据位置,这样才可以给每个线程块制定运算的数据
//将threadIdx/BlockIdx映射到像素位置
int x = threadIdx.x + blockIdx.x * blockDim.x;
int y = threadIdx.y + blockIdx.y * blockDim.y;
//blockIdx.y*blockDim.y代表每一横行的线程数
int tid = x + y * blockDim.x * gridDim.x;
for (tid = 0; tid < DATA_SIZE; tid++) {
sum += num[tid] * num[tid] * num[tid];
}
*result = sum;
}
int main( )
{
//pointer for host memory
int *h_a;
//pointer for device memory(GPU)
int *d_a;
//GPU上计算结果定义并分配内存
int *result;
cudaMalloc((void**)&result, sizeof(int));
// Part 1 of 5: allocate host and device memory
size_t memSize = sizeof(int)* DATA_SIZE;
h_a = (int *) malloc(memSize);
//cudaMalloc()第一个参数是一个指针,用于保存新分配内存地址的变量,第二个是内存大小
cudaMalloc((void**)&d_a,memSize);
//调用生成随机数的函数,生成随机数的位置在host端*h_a,在这里要注意分配内存的顺序
//该函数中的参数h_a一定要先分配内存
GenerateNumbers(h_a,DATA_SIZE);
//cudaMemcpy(目的,源,要拷贝的大小,方向)
//将h_a中的生成的随机数传输给d_a,源是host
cudaMemcpy(d_a,h_a,sizeof(int)* DATA_SIZE, cudaMemcpyHostToDevice);
// Part 2 of 5: configure and launch kernel
//通过dim3 声明一个二维格,其实是三维但是CUDA运行时会自动把第三维制定为1
dim3 dimGrid(2,4);
dim3 dimBlock(4,16);
//myFirstKernel参数即要传给核函数的值
myFirstKernel<<< dimGrid,dimBlock >>>(d_a,result);
// block until the device has completed
cudaThreadSynchronize();
//printf("GPUsum: %d\n", *result);
// check if kernel execution generated an error
checkCUDAError("kernel execution");
int SumResult;
// Part 4 of 5: device to host copy
cudaMemcpy(&SumResult,result,sizeof(int),cudaMemcpyDeviceToHost);
// Check for any CUDA errors
checkCUDAError("cudaMemcpy");
// Part 5 of 5: verify the data returned to the host is correct
//常会用到assert()断言判断计算是否正确
printf("Sum Result: %d \n", SumResult);
// free device memory
cudaFree(d_a);
cudaFree(result);
// free host memory
free(h_a);
return 0;
}
void checkCUDAError(const char *msg)
{
cudaError_t err = cudaGetLastError();
if( cudaSuccess != err)
{
fprintf(stderr, "Cuda error: %s: %s.\n", msg, cudaGetErrorString( err) );
exit(-1);
}
}设备属性的使用
如要进行双精度浮点计算的应用程序,需要1.3或更高版本的显卡才能支持双精度浮点数学计算,因此根据cudaDGetDeviceCount()和cudaGetDeviceProporties()中返回的结果,对每个设备进行迭代,查找主版本号大于1,或者主版本号为1且次版本号大于等于3的设备。cuda提供了一种自动的方式来执行这种迭代操作
cudaDeviceProp prop;
int dev;
cudaGetDevice(&dev);
memset(&prop,0,sizeof(cudaDeviceProp) );
prop.major = 1;
prop.minor = 3;
cudaChooseDevice(&dev,&prop);
cudaSetDevice(dev);在完成集群调度策略优化的时候,会考虑各设备间的异构性,为在不同调度策略中可以通过设备属性的选择完成调度策略的应用。
共享内存
共享内存会将CUDA C的关键字 __share__ 添加到变量声明中,表示这一变量将驻留在共享内存中。对于共享内存来说,GPU在启动每一个线程块时,都会为该线程块创建一个该共享内存变量的一个副本,线程块中的每个线程都将共享这块内存,但线程块中的线程无法看到或修改其它线程块的变量副本,即共享内存是对于线程块中的线程来说,是同一线程块中的线程可以进行通信和协作。
cuda设备属性中会限制在一个线程块中可使用的最大共享内存数量。
如果有N个数据元素,那么通常只需要N个线程来计算点积,为了防止线程过多的情况,线程的数量应为ThreadPreBlock的最小整数倍,并写要大于等于N,可以采用如下计算公式
(N+(ThreadPreBlock-1)/ThreadPreBlock)
当某些线程需要执行一条指令,而其他线程不需要执行是,这种情况称为线程发散,如只有奇数索引的线程才执行__syncthreads(),由于要确保在每个线程都执行完__syncthreads(),硬件将使这些未执行的线程保持等待,永久的等下去。
CUDA流
要想使用多个流,需要检查设备是否支持重叠功能,方法如下在CUDA的安装目录下有
/NVIDIA_CUDA-8.0_Samples/1_Utilities/deviceQuery文件,对文件中DeviceQuery 进行修改在输出部分添加输出 deviceProp.deviceOverlap? "Enable":"Disable" GPU是否支持设备重叠(Device Overlap)功能,支持设备重叠功能的GPU能够在执行一个CUDA C核函数的同时,还能在设备与主机之间执行复制等操作。