一个基本的hello cuda程序包含以下三个部分:
#include
void cpu(){
printf("hello cpu\n");
}
// 1. 加上 __global__ 前缀
__global__ void gpu(){
printf("hello gpu\n");
}
int main() {
cpu();
// 2. 加上<<>> 线程块的个数与线程的个数
gpu<<<1,1>>>();
// 3. 加上cudaDeviceSynchronize(), 等待GPU代码执行完成,才能在cpu上恢复执行
cudaDeviceSynchronize();
return 0;
}
一块GPU拥有多个Grid,一个Grid含有多个Block,一个Block中含有多个Thread,每个Thread都可以并行处理一个程序,下图黑框表示一个Grid, 蓝色框表示Block,白色框表示 Thread
gridDim.x:表示一个Grid里面Block块数量(图示为2)
blockIdx.x:表示当前Block在Grid中的索引
blockDim.x:表示一个Block中x方向上Thread数量
threadIdx.x:表示当前Thread在对应Block中的索引
<<
启动核函数时,核函数代码在每个已配置的线程块中的每个线程中都执行;
设置 function<<<2, 2>>> 则表示创建两个block,每个block有2个线程来同时执行function函数:
#include
void cpu(){
printf("hello cpu\n");
}
// 1. 加上 __global__ 前缀
__global__ void gpu(){
printf("hello gpu\n");
}
int main() {
// 2. 加上<<>> 线程块的个数与线程的个数
gpu<<<2,2>>>(); //创建两个Block,每个block有2个thread来执行该函数
// 3. 加上cudaDeviceSynchronize(), 等待GPU代码执行完成,才能在cpu上恢复执行
cudaDeviceSynchronize();
return 0;
}
若只需要在block 0 的 thread 0 来进行一些操作:就需要使用 blockIdx与 threadIdx,若只在第一个block的第一个thread输出,只需要将函数修改为:
__global__ void gpu(){
// 在第一个block的第一个thread输出
if(blockIdx.x ==0 && threadIdx.x==0) {
printf("hello gpu\n");
}
}
// 一个block中的线程数量 * 当前block的Id + 当前线程在当前block中的id (将二维索引转换为1维索引)
int threadi = blockDim.x * blockIdx.x + threadIdx.x;
cudaMallocManaged(&ptr, size):
该函数既可以为cpu预分配内存,也可以为gpu预分配内存,分配UM时,内存尚未驻留在主机或设备上。当主机或设备尝试访问内存时会发生 页错误,此时设备会将数据进行迁移。同理,当GPU尝试访问尚未驻留在显存上的数据时会触发缺页中断并将数据进行迁移,这个过程是耗时的。
#include
#include
int main() {
// 定义预分配内存的大小
const int N = 100;
size_t size = N * sizeof(int);
// 在cpu上预分配内存
int *a = (int*)malloc(size);
free(a);
// 在Gpu上预分配统一内存
int *b;
cudaMallocManaged(&b, size); // 可以给cpu预分配内存,也可以给gpu预分配内存
cudaFree(b);
return 0;
}
数据迁移cudaMemPrefetchAsync(&ptr, size, DeviceId):
在即将要在gpu上使用某个数据时提前开辟Gpu显存空间,避免缺页中断导致的效率低问题,同理在Gpu运算完成后,后面通过cpu处理该数据前需要在cpu预分配内存;
#include
#include
int main() {
// 获取设备id
int id;
cudaGetDevice(&id);
// 定义预分配内存的大小
const int N = 100;
size_t size = N * sizeof(int);
// 统一分配内存(cpu、gpu都可以使用)
int *a;
cudaMallocManaged(&a, size);
// cpu上使用a
// 一系列操作
// 准备在gpu上使用a时,提前在显存上开辟空间(避免缺页中断,导致效率低下)
cudaMemPrefetchAsync(a, size, id);
// 在gpu上使用a
// 一系列操作
// 即将cpu使用a之前,将数据迁移到cpu,防止发生缺页中断
cudaMemPrefetchAsync(a, size, cudaCpuDeviceId);
// 在cpu上使用a
// 一系列操作
// 释放空间
cudaFree(a);
return 0;
}
将数组中的每个元素扩大两倍
#include
#include
void init_a(int *a, int len){
for(int i=0;i<len;++i){
a[i] = i;
}
}
// cuda函数专用标志符,且不能有返回值
__global__ void gpu(int* a, int N) {
// 计算当前的线程索引
int threadi = blockIdx.x * blockDim.x + threadIdx.x;
// 每个线程处理一个位置
if (i < N) {
a[i] = i*2;
}
}
int main() {
// 获取设备id
int id;
cudaGetDevice(&id);
// 数组的长度
const int N = 5;
// 需要的内存空间
size_t size = N * sizeof(int);
int* a;
// 分配内存空间,cpu和gpu都可以使用
cudaMallocManaged(&a, size);
// 初始化数组
init_a(a, N);
// 提前开辟显存空间
cudaMemPrefetchAsync(a, size, id);
// 每个block都有256个线程
size_t threads_num = 256;
// 计算需要多少个block,向上取整,让数组的每一位拥有一个独立的线程
size_t blocks_num = (N + threads_num - 1) / threads_num;
// 使用cuda计算
gpu<<<blocks_num, threads_num>>>(a, N);
// 将数据迁移到cpu
cudaMemPrefetchAsync(a, size, cudaCpuDeviceId);
// 等待同步
cudaDeviceSynchronize();
// 释放内存
cudaFree(a);
return 1;
}
// 在cpu和gpu都注册一块内存
int *host_a, *device_a;
cudaMalloc(&device_a, size); // 在gpu上分配显存
initializeOnHost(host_a, N); // 将cpu内存空间初始化
// 将cpu内存上的值host_a拷贝到显存device_a上
cudaMemcpy(device_a, host_a, size, cudaMemcpyHostToDevice);
// 在gpu上执行核函数
kenel_function<<<blocks_num, thread_num, 0, someStream>>>(device_a, N);
// 将显存中的值拷device_a贝到cpu内存host_a中
cudaMemcpy(host_a, device_a, size, cudaMemcpyDeviceToHost);
verifyOnHost(host_a, N);
// 释放内存和显存
cudaFree(device_a);
cudaFreeHost(host_a);
当数据量大于总的分配线程数时,需要将数据切分成块来运行,即以跨步循环的方式运行;如:当数组长度为32而注册的总线程数为8时,需要分4次来 循环处理数组中的元素。
#include
#include
void init_a(int *a, int len){
for(int i=0;i<len;++i){
a[i] = i;
}
}
// cuda函数专用标志符,且不能有返回值
__global__ void gpu(int* a, int N) {
// 计算当前的线程索引
int threadi = blockIdx.x * blockDim.x + threadIdx.x;
// 计算总的线程数量,每次都要跨这么多步(线程0处理完a[0]位置后处理a8],然后处理a[16], 然后处理a[24])
int total_thread = gridDim.x * blockDim.x;
// 每个线程处理一个位置
for(int i=threadi;i<N;i+=total_thread){
a[i]*=2;
}
}
int main() {
// 数组的长度
const int N = 32;
// 需要的内存空间
size_t size = N * sizeof(int);
int* a_cpu, a_gpu;
cudaMallocHost(&a_cpu, size); // cpu 上分配内存
cudaMall(&a_gpu, size); // gpu 上分配显存
// cpu上初始化数组
init_a(a_cpu, N);
// 将cpu数据copy到gpu中
cudaMemcpy(a_gpu, a_cpu, size, cudaMemcpyHostToDevice);
// gpu操作数据
size_t blocks_num = 2;
size_t threads_num = 4;
gpu<<<blocks_num, threads_num>>>(a_gpu, N);
// gpu数据copy到cpu
cudaMemcpy(a_cpu, a_gpu, size, cudaMemcpyDeviceToHost);
// 同步
cudaDeviceSynchronize();
// 释放内存
cudaFree(a_gpu);
cudaFreeHost(a_cpu);
return 1;
}
#include
#include
#include
inline cudaError_t checkCuda(cudaError_t result){
if(result != cudaSuccess){
fprintf(stderr, "CUDA runtime error: %s\n", cudaGetErrorString(result));
assert(result == cudaSuccess);
}
return result;
}
int main() {
// 数组的长度
const int N = 32;
// 需要的内存空间
size_t size = N * sizeof(int);
int* a;
// 检查cuda函数是否出错
checkCuda(cudaMallocManaged(&a, size));
return 0;
}
#include
#include
#define N 64 // 矩阵大小为 64*64
__global__ void gpu(int *a, int *b, int *c_gpu) {
// 找出线程对应的坐标位置
int r = blockDim.x * blockIdx.x + threadIdx.x;
int c = blockDim.y * blockIdx.y + threadIdx.y;
if (r < N && c < N) {
c_gpu[r * N + c] = a[r * N + c] + b[r * N + c];
}
}
void cpu(int* a, int* b, int* c_cpu) {
for (int r = 0; r < N; r++) {
for (int c = 0; c < N; c++) {
c_cpu[r * N + c] = a[r * N + c] + b[r * N + c];
}
}
}
int main() {
// 定义两个数组和结果存储数组
int* a, * b, * c_cpu, * c_gpu;
size_t size = N * N * sizeof(int);
cudaMallocManaged(&a, size);
cudaMallocManaged(&b, size);
cudaMallocManaged(&c_cpu, size);
cudaMallocManaged(&c_gpu, size);
// 数组初始化
for (int i = 0; i < N; ++i) {
for (int j = 0; j < N; ++j) {
a[i * N + j] = 1;
b[i * N + j] = 2;
c_cpu[i * N + j] = 0;
c_gpu[i * N + j] = 0;
}
}
// 获取设备id
int id;
cudaGetDevice(&id);
// 提前在显存上开辟空间
cudaMemPrefetchAsync(a, size, id);
cudaMemPrefetchAsync(b, size, id);
// 定义block中每个方向的线程的数量(分为两个方向,为了方便计算矩阵)
dim3 threads(16, 16, 1);
// 计算需要每个方向上要开多少个blocks,才能得到64*64个线程来处理两个矩阵相加
dim3 blocks((N + threads.x - 1) / threads.x, (N + threads.x - 1) / threads.x, 1);
// 使用cpu计算两个矩阵加法
cpu(a, b, c_cpu);
// 使用gpu计算矩阵加法
gpu << <blocks, threads >> > (a, b, c_gpu);
// 同步
cudaDeviceSynchronize();
// 释放空间
cudaFree(a);
cudaFree(b);
cudaFree(c_cpu);
cudaFree(c_gpu);
return 1;
}
cuda中的流遵循以下规则:
sudaStream_t stream; // 创建流
cudaStreamCreate(&stream);
// 执行核函数
kernel_function<<<blocks_num, threads_num, 0, stream>>>();
// 销毁流
cudaStreamDestroy(stream);