原子操作
CUDA 编程的基本思想利用 GPU 来尽可能地并行执行相同的核函数,对于大多数并行任务,线程间不需要合作或使用其他线程的资源,只需要保证自己能够正常执行即可
但对于某些需要同步执行的操作,例如多个核函数需要对同一个变量进行读取-修改-写入,由于核函数之间是异步的,当试图同时执行时,就会导致出现问题
CUDA 的原子操作是针对全局内存或共享内存中的变量,其是对全局变量或共享变量进行读取-修改-写入这三个操作的一个最小单位的执行过程,在这个执行过程中,不允许其他并行线程对该变量进行读取和写入
也就是说,原子操作实现了多个线程间共享的变量的互斥保护,每次只能有一个线程对全局变量或共享变量进行读写操作,能够确保任何一次对变量的操作的结果的正确性
常用函数
原子操作的常用函数如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| atomicAdd(&value, num);
atomicSub(&value, num);
atomicExch(&value, num);
atomicMax(&value, num);
atomicMin(&value, num);
atomicInc(&value, num);
atomicDec(&value, num);
atomicCAS(&value, compare, val);
atomicAnd(&value, compare, val);
atomicOr(&value, compare, val);
atomicXor(&value, compare, val);
|
实例
下面代码给出使用原子操作求和的实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| #include <stdio.h> #define N 10 #define BLOCKS 32 #define BLOCK_SIZE 256
__global__ void sum_gpu(int *data, int *res) { int index = blockDim.x * blockIdx.x + threadIdx.x; if (index < N) { atomicAdd(res, data[index]); } }
int sum_cpu(int *data) { int sum = 0; for (int i = 0; i < N; i++) { sum += data[i]; } return sum; }
void check(int res_cpu, int res_gpu) { printf("CPU 计算结果:%d\n", res_cpu); printf("GPU 计算结果:%d\n", res_gpu); printf("%s\n", res_cpu == res_gpu ? "Pass" : "Error"); }
int main() {
int *data, *res; cudaMallocManaged(&data, sizeof(int) * N); cudaMallocManaged(&res, sizeof(int));
res[0] = 0; for (int i = 0; i < N; i++) { data[i] = rand() % 10; }
int res_cpu = sum_cpu(data); sum_gpu<<<BLOCKS, BLOCK_SIZE>>>(data, res); cudaDeviceSynchronize(); int res_gpu = res[0];
check(res_cpu, res_gpu); cudaFree(data); cudaFree(res);
return 0; }
|