voidhistogram_sequential(unsignedchar *data, int *histogram, int n) { // 初始化 for (int i = 0; i < 256; i++) { histogram[i] = 0; } // 统计 for (int i = 0; i < n; i++) { histogram[data[i]]++; } }
时间复杂度 O(n),空间复杂度 O(桶数)。
并行化的挑战
尝试直接并行化:
1 2 3 4 5 6
__global__ void histogram_naive(unsigned char *data, int *histogram, int n) { int i = blockIdx.x * blockDim.x + threadIdx.x; if (i < n) { histogram[data[i]]++; // 危险!读-改-写竞争 } }
__global__ void histogram_atomic(unsigned char *data, int *histogram, int n) { int i = blockIdx.x * blockDim.x + threadIdx.x; if (i < n) { atomicAdd(&histogram[data[i]], 1); // 原子加 } }
__global__ void histogram_coarsened(unsigned char *data, int *histogram, int n) { __shared__ int private_hist[NUM_BINS]; // 初始化 for (int i = threadIdx.x; i < NUM_BINS; i += blockDim.x) { private_hist[i] = 0; } __syncthreads(); // 每线程处理连续的 COARSEN_FACTOR 个元素 int base = (blockIdx.x * blockDim.x + threadIdx.x) * COARSEN_FACTOR; for (int k = 0; k < COARSEN_FACTOR; k++) { int idx = base + k; if (idx < n) { atomicAdd(&private_hist[data[idx]], 1); } } __syncthreads(); // 合并 for (int i = threadIdx.x; i < NUM_BINS; i += blockDim.x) { atomicAdd(&histogram[i], private_hist[i]); } }
__global__ void histogram_register(unsigned char *data, int *histogram, int n) { // 每线程私有直方图(寄存器) int local_hist[8] = {0}; int i = blockIdx.x * blockDim.x + threadIdx.x; int stride = blockDim.x * gridDim.x; while (i < n) { int bin = data[i] % 8; // 假设只有 8 个桶 local_hist[bin]++; i += stride; } // 合并到全局 for (int b = 0; b < 8; b++) { atomicAdd(&histogram[b], local_hist[b]); } }
优势:寄存器最快,无争用。
限制:桶数必须很少(寄存器数量有限)。
多级私有化
对于大桶数:
1
寄存器(极少桶)→ 共享内存(中等桶)→ 全局内存(大桶数)
每级容量递增,速度递减。
原子操作的硬件支持
支持的数据类型
类型
原子操作支持
备注
int
全部
最常用
unsigned
全部
float
atomicAdd
Kepler+ (CC 3.0)
double
atomicAdd
Pascal+ (CC 6.0)
half
atomicAdd
Volta+ (CC 7.0)
共享内存 vs 全局内存原子
特性
共享内存原子
全局内存原子
延迟
~20 周期
~400 周期
带宽
高
低
争用范围
Block 内
全设备
适用场景
中间结果
最终结果
原子操作实现原理
Compare-And-Swap (CAS):
1 2 3 4 5 6 7 8 9
// atomicAdd 的本质实现 __device__ int atomicAdd_manual(int *addr, int val) { int old = *addr, assumed; do { assumed = old; old = atomicCAS(addr, assumed, assumed + val); } while (old != assumed); return old; }
循环直到成功——高争用时可能循环很多次。
应用扩展
多通道直方图
RGB 图像的三通道直方图:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
__global__ void histogram_rgb(unsigned char *image, int *hist_r, int *hist_g, int *hist_b, int n) { __shared__ int priv_r[256], priv_g[256], priv_b[256]; // 初始化 // ... int i = blockIdx.x * blockDim.x + threadIdx.x; if (i < n) { atomicAdd(&priv_r[image[3*i + 0]], 1); atomicAdd(&priv_g[image[3*i + 1]], 1); atomicAdd(&priv_b[image[3*i + 2]], 1); } __syncthreads(); // 合并 // ... }
加权直方图
每个数据点有权重:
1
atomicAdd(&histogram[data[i]], weight[i]);
用于直方图均衡化等场景。
二维直方图
统计两个变量的联合分布:
1 2 3 4
int bin_x = data_x[i] / bin_width_x; int bin_y = data_y[i] / bin_width_y; int bin = bin_y * num_bins_x + bin_x; atomicAdd(&histogram_2d[bin], 1);