GPU编程
GPU结构
每个GPU中有若干个GPC(图形计算簇),每个GPC中又有若干个TPC(Texture Processing cluster), 每个TPC中有若干个SM(Stream Multiprocessor),每个SM中有若干个block,在block中有各种各样的核和寄存器。
在GPU编程中,通常把cpu称作host,把gpu称作device。
计算过程:
- 数据从cpu拷贝到gpu显存中
- 加载GPU程序并运行
- 将运行完后的数据又送回CPU中
CUDA编程
基本结构:malloc();
cudamalloc(); //分配显存
cudaMemcpy(): //将内存中的内容放到显存中
kernelName<<<gridSize, blockSize>>>(input_params);//计算过程,input_params是运行的函数
cudaMemcpy();
cudaFree();
例如:__global__ void add(int *a, int *b, int *c)
{
*c = *a + *b;
}
int main(void)
{
int a, b, c;// host copies of a, b, c
int *d_a, *d_b, *d_c;// device copies of a, b, c
in tsize = sizeof(int);// Allocate space for device copies of a, b, c,
分配内存
cudaMalloc((void**)&d_a, size);
cudaMalloc((void**)&d_b, size);
cudaMalloc((void**)&d_c, size);// Setup input values,
设置输入数据
a = 2;
b = 7;
// Copy inputs to device,拷贝数据到GPU的显存
cudaMemcpy(d_a, &a, size, cudaMemcpyHostToDevice);
cudaMemcpy(d_b, &b, size, cudaMemcpyHostToDevice);
// Launch add() kernel on GPU,执行kernel
add<<<1,1>>>(d_a, d_b, d_c);
// Copy result back to host,数据拷贝回host内存
cudaMemcpy(&c, d_c, size, cudaMemcpyDeviceToHost);
// Cleanup,释放内存cudaFree(d_a);
cudaFree(d_b); cudaFree(d_c);return 0;
}
函数前的标识:
__global__
: 表示在device上执行,从host中调用。它的返回类型必须是void,并且它不会等待函数执行完主线程就会进行下一步。__device__
: 在device上执行,并且从device中调用__host__
: host执行,host调用,默认值
函数中变量类型:
__device__
: grid中所有线程都可以访问__constant__
: 常量内存__shared__
: 只有同一个block中的线程才可以访问
- <<
>>: 调用的基本结构为 grid(网格)->block(块)->thread(线程).例如<<<1, 1>>>代表这个网格中有一个块,这个块中有一个线程。1,>
grid和thread都是dim3型变量,dim3可以看成包含三个整数的结构体。我们可以自由的定义grid或thread为一维、二维或三维,他们的默认值都是1。例如:dim3 block(3, 3);
add<<<1, block>>>(...);
表示有一个块,每块中有(3, 3)个线程
上面的例子可以变成
add<<<N, 1>>>(d_a, d_b, d_c);
__global__ void add(int *a, int *b, int *c)
{
c[blockIdx.x] = a[blockIdx.x] + b[blockIdx.x];
}
每一个线程都可以用(blockIdx, threadIdx)进行标识,这两个变量都是dim3类型的,因此上面的例子中就可以使用blockIdx.x表示这是第几个block.
变量:
- gridDim: grid维度,dim3型
- blockDim: block维度
- blockIdx:
- threadIdx:
- warpSize: 大小固定为32
每个grid都是在一个SM上的,而SM空间有限,因此每次可以使用的线程是有上限的,一般是1024.
内存分配:
内存分为两种
- 可分配内存: malloc()/new() 操作所得到的内存,这种内存由cpu进行管理,可能被分页机制换下
分页锁定内存: 这种内存受gpu控制,不受cpu调度影响,可以使用dma直接读取
cudaMallocHost(void **ptr, size_t size)
cudaHostAlloc(void **pHost, size_t size, unsigned int flags)
:- cudaHostAllocPortable:多个CPU线程都可访问
- cudaHostAllocWriteCombined:数据传输快,但只允许host写(读速度慢)
- cudaHostAllocMapped:CPU/GPU都可访,属于内存映射
cudaHostRegister( void* ptr, size_tsize, unsigned intflags )
: 可分页内存转换为分页锁定内存
显存分配
- cudaMalloc(): 一维数组,数据连续存储
- cudaMallocPitch(): 二维数组,会自动对齐,可能会浪费部分内存空间
- cudaMalloc3D():三维数组
统一寻址
统一寻址指的是系统自动在host和device之间搬运数据,同时保证host和device都可访问,并且需要进行显式同步。(因为系统会在运行函数时同时进行下一步)
例如:__global__ void add(int *ret, int a, int b)
{
ret[threadIdx.x] = a + b + threadIdx.x;
}
int main()
{
int *ret;
cudaMallocManaged(&ret, 1000 * sizeof(int));//自动进行显存内存分配及数据搬运
add<<<1, 1000>>>(ret, 10, 100);
cudaDeviceSynchronized();//进行同步
for(int i=0; i<1000; i++)
{
printf("%d", ret[i]);
}
cudaFree(ret);
return 0;
}