原子函数
原子函数对驻留在全局内存或共享内存中的数据执行读 - 修改 - 写原子操作。原子函数只能在设备函数中使用。
概述
原子函数是并行编程中的重要工具,用于避免多个线程同时访问共享数据时的竞争条件。MUSA 提供以下类型的原子函数:
- 算术原子函数: atomicAdd, atomicSub, atomicInc, atomicDec, atomicExch
- 比较并交换: atomicCAS
- 位原子操作: atomicAnd, atomicOr, atomicXor
任何原子操作都可以基于 atomicCAS()(比较并交换)实现。
算术原子函数
atomicAdd()
int atomicAdd(int* address, int val);
unsigned int atomicAdd(unsigned int* address, unsigned int val);
unsigned long long int atomicAdd(unsigned long long int* address, unsigned long long int val);
float atomicAdd(float* address, float val);
double atomicAdd(double* address, double val);
读取位于全局或共享内存中地址 address 存储的值 old,计算 (old + val),并将结果存储回同一地址。返回 old。
示例:
__global__ void countKernel(int* counter, int n) {
int idx = blockIdx.x * blockDim.x + threadIdx.x;
if (idx < n) {
// 原子增加计数器
atomicAdd(counter, 1);
}
}
// 主机代码
int* d_counter;
int h_counter = 0;
musaMalloc(&d_counter, sizeof(int));
musaMemcpy(d_counter, &h_counter, sizeof(int), musaMemcpyHostToDevice);
countKernel<<<gridSize, blockSize>>>(d_counter, 10000);
musaMemcpy(&h_counter, d_counter, sizeof(int), musaMemcpyDeviceToHost);
printf("Counter: %d\n", h_counter); // 输出:10000
atomicSub()
int atomicSub(int* address, int val);
unsigned int atomicSub(unsigned int* address, unsigned int val);
读取位于全局或共享内存中地址 address 存储的 32 位值 old,计算 (old - val),并将结果存储回同一地址。返回 old。
atomicExch()
int atomicExch(int* address, int val);
unsigned int atomicExch(unsigned int* address, unsigned int val);
unsigned long long int atomicExch(unsigned long long int* address, unsigned long long int val);
float atomicExch(float* address, float val);
读取位于全局或共享内存中地址 address 存储的值 old,将 val 存储到同一地址。返回 old。
atomicInc()
unsigned int atomicInc(unsigned int* address, unsigned int val);
读取位于全局或共享内存中地址 address 存储的 32 位值 old,计算 ((old >= val) ? 0 : (old+1)),并将结果存储回同一地址。返回 old。
示例 - 循环计数器:
// address 处的值从 0 递增到 val-1,然后回绕到 0
unsigned int old = atomicInc(counter, 100); // 0 -> 1 -> 2 -> ... -> 99 -> 0 -> ...
atomicDec()
unsigned int atomicDec(unsigned int* address, unsigned int val);
读取位于全局或共享内存中地址 address 存储的 32 位值 old,计算 (((old == 0) || (old > val)) ? val : (old-1)),并将结果存储回同一地址。返回 old。
atomicCAS()
int atomicCAS(int* address, int compare, int val);
unsigned int atomicCAS(unsigned int* address, unsigned int compare, unsigned int val);
unsigned long long int atomicCAS(unsigned long long int* address,
unsigned long long int compare,
unsigned long long int val);
unsigned short int atomicCAS(unsigned short int* address,
unsigned short int compare,
unsigned short int val);
比较并交换 (Compare And Swap): 读取位于全局或共享内存中地址 address 存储的值 old,计算 (old == compare ? val : old),并将结果存储到同一地址。返回 old。
示例 - 实现自旋锁:
__device__ void lock(int* mutex) {
while (atomicCAS(mutex, 0, 1) != 0) {
// 自旋等待
}
}
__device__ void unlock(int* mutex) {
atomicExch(mutex, 0);
}
__global__ void criticalSection(int* mutex, int* shared_data) {
lock(mutex);
// 临界区代码
*shared_data += 1;
unlock(mutex);
}
位原子操作
atomicAnd()
int atomicAnd(int* address, int val);
unsigned int atomicAnd(unsigned int* address, unsigned int val);
unsigned long long int atomicAnd(unsigned long long int* address, unsigned long long int val);
读取位于全局或共享内存中地址 address 存储的值 old,计算 (old & val),并将结果存储回同一地址。返回 old。
atomicOr()
int atomicOr(int* address, int val);
unsigned int atomicOr(unsigned int* address, unsigned int val);
unsigned long long int atomicOr(unsigned long long int* address, unsigned long long int val);
读取位于全局或共享内存中地址 address 存储的值 old,计算 (old | val),并将结果存储回同一地址。返回 old。
atomicXor()
int atomicXor(int* address, int val);
unsigned int atomicXor(unsigned int* address, unsigned int val);
unsigned long long int atomicXor(unsigned long long int* address, unsigned long long int val);
读取位于全局或共享内存中地址 address 存储的值 old,计算 (old ^ val),并将结果存储回同一地址。返回 old。
高级用法
基于 atomicCAS 实现自定义原子操作
// 双精度浮点原子加
__device__ double atomicAdd(double* address, double val) {
unsigned long long int* address_as_ull = (unsigned long long int*)address;
unsigned long long int old = *address_as_ull, assumed;
do {
assumed = old;
old = atomicCAS(address_as_ull, assumed,
__double_as_longlong(val + __longlong_as_double(assumed)));
// 注意:使用整数比较以避免 NaN 问题 (NaN != NaN)
} while (assumed != old);
return __longlong_as_double(old);
}