【CUDA】CUDA C++介绍
并行计算:以vector add为例,计算output中第一个元素的过程和计算第二、三个元素的过程独立。CPU+高速总线(PCIE/NVLink)+GPU(用于数学/科学计算,有自己的操作系统)main函数调用该kernel时,也需要修改调用参数。改写add()来使用并行线程,而非并行blocks。使用threadIdx.x代替blockIdx.x。术语:一个block可以被划分为并行线程。但是不
·
1. 异构编程
CPU+高速总线(PCIE/NVLink)+GPU(用于数学/科学计算,有自己的操作系统)
三步走:
- 从GPU内存往CPU内存拷贝输入数据
- 加载GPU代码并执行,在片上缓存数据来提升性能
- 从GPU内存往CPU内存拷贝结果
并行计算:以vector add为例,计算output中第一个元素的过程和计算第二、三个元素的过程独立
2. GPU kernels: device code
函数定义
__global__ void mykernel(void) {
}
- CUDA C++关键字/装饰器 __global__ 表示这个函数运行在GPU上(给编译器的提示),通过host code或其它device code来调用
- NVCC:编译器。用于把源码划分为host和device部分
- device函数由NVIDIA编译器来执行
- host函数由gcc,cl.exe等标准host编译器执行
函数调用
mykernel<<<1, 1>>>();
- 三层尖括号表示调用device code(也叫做kernel launch),括号内的参数是CUDA核执行配置
- 第一个参数是warp数,第二个参数是每个warp包含的线程数
内存管理
- host和device侧的内存是完全分隔的
- device侧的指针指向GPU内存,通常传递给device code,不在host侧解引用
- host侧的指针指向CPU内存,通常不传给device code,不在device侧解引用
- 特例:pinned pointers,ATS,managed memory
- 用于device侧内存管理的API(用法和C语言API类似)
- cudaMalloc()
- cudaFree()
- cudaMemcpy()
- 这些API使用指针来分配、释放或者复制内存。指针只是一个数,它没有metadata,不是正式的C++对象。
3. 设备侧向量加法
__global__ void add(int *a, int *b, int *c){
c[blockIdx.x] = a[blockIdx.x] + b[blockIdx.x];
}
- grid是线程和block的组合,是一种分层描述。grid - block(warp/warps) - threads
- blockIdx是一个结构体/内置变量,有三个元素:.x,.y,.z。每个元素的索引从0开始,到N-1结束,其中N是kernel launch时传入的值。
#define N 512
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
int size = N * sizeof(int);
// Alloc space for device dopies of a, b, c
cudaMalloc((void **)&d_a, size);
cudaMalloc((void **)&d_b, size);
cudaMalloc((void **)&d_c, size);
// Alloc space for host copies of a, b, c and setup input values
a = (int *)malloc(size); random_ints(a, N);
b = (int *)malloc(size); random_ints(b, N);
c = (int *)malloc(size);
// Copy inputs to device
cudaMemcpy(d_a, a, size, cudaMemcpyHostToDevice); // 可以不加cudaMemcpyHostToDevice,但是加上相当于提供了一层校验
cudaMemcpy(d_b, b, size, cudaMemcpyHostToDevice);
// Launch add() kernel on GPU with N blocks (N copies of add())
add<<<N,1>>>(d_a, d_b, d_c);
// Copy result back to host
cudaMemcpy(c, d_c, size cudaMemcpyDeviceToHost);
// Cleanup
free(a); free(b); free(c);
cudaFree(d_a); cudaFree(d_b); cudaFree(d_c);
return 0;
}
- cuda开头的API:cuda runtime API。如果执行错误,会返回错误码
- cuda符合C++ 2014标准,但是不支持C++标准库
4. CUDA线程
-
术语:一个block可以被划分为并行线程
-
改写add()来使用并行线程,而非并行blocks
__global__ void add(int *a, int *b, int *c){ c[threadIdx.x] = a[threadIdx.x] + b[threadIdx.x]; }使用threadIdx.x代替blockIdx.x
main函数调用该kernel时,也需要修改调用参数
add<<<1, N>>>();
5. 线程块和线程
- 如果每个block有M个线程,则 int index = threadIdx.x + blockIdx.x * M;
__global__ void add(int *a, int *b, int *c){
int index = threadIdx.x + blockIdx.x * blockDim.x;
c[index] = a[index] + b[index];
}
#define N (2048*2048)
#define THREAD_PRE_BLOCK 512
...
add<<<N/THREAD_PRE_BLOCK, THREAD_PRE_BLOCK>>>(d_a, d_b, d_c);
...
6. 处理任意大小的向量
__global__ void add(int *a, int *b, int *c, int n){
int index = threadIdx.x + blockIdx.x * blockDim.x;
if (index < n){
c[index] = a[index] + b[index];
}
}
add<<<(N + M - 1) / M, M>>>(d_a, d_b, d_c, N);
相同块内的线程可以互相沟通和同步。但是不同块中的线程没有这个能力。
Q&A
- cudaMemcpy通过高速总线实现数据传输。会有传输耗时,它的带宽比GPU内部带宽要小,会限制GPU的性能。因此尽量减少host和device侧的数据传输。
- host和device侧的size可以确保是一致的,不会有LP/LLP差异,如host侧的long是64bit,那么device侧的long也是64bit。但在host侧使用的编译器类型受cuda限制。
- grid的大小通常是根据问题规模来确定的
- threadIdx<1024,没有其他限制
- host和device侧的代码可以放在两个文件中,只要能用C/C++方式编译起来
更多推荐


所有评论(0)