CUDA计算直方图(一)原子操作 atomicAdd

本文探讨了使用CUDA并行计算技术加速直方图计算的方法,对比了CPU串行算法和GPU并行算法的性能,展示了如何利用原子操作atomicAdd进行高效的数据处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

参考: Shane Cook. CUDA Programming: A developer’s guide to parallel computing with GPUs

背景

计算直方图是图像处理和机器学习等常用的操作.
对于大数据集, 使用串行算法十分浪费时间.
这里使用CUDA来加速直方图的计算.
对于一个较大的整数数组, 值域0~255. 求取直方图.

使用CPU计算

void cpuHist(Cuda8u *pHist_data, Cuda32u* pBin_data, Cuda32u arraySize, Cuda32u binSize)
{
	for (Cuda32u i = 0; i < arraySize;i++)
	{
		if (pHist_data[i] < binSize)
		{
			pBin_data[pHist_data[i]]++;
		}
	}

}

main函数调用:

	// CPU 数据初始化
    const Cuda32u uArraySize = 256*256;
	const Cuda32u uBinSize = 256;
	Cuda8u *h_puchData = (Cuda8u *)malloc(uArraySize*sizeof(Cuda8u));
	for (int i = 0; i < uArraySize; i++)
	{
		h_puchData[i] = rand() % uBinSize;
	}
	Cuda32u h_puHist[uBinSize] = { 0 };
	Cuda32u N = 64;
	Cuda32u iIterNum = 10;
	// 使用CPU计算
	StartTimer();
	for (Cuda32u i = 0; i < iIterNum;i++)
	{
		cpuHist(h_puchData, h_puHist, uArraySize, uBinSize);
	}
	double dblTimeElps = GetTimer();
	Cuda32u iSumC = 0;
	for (Cuda32u i = 0; i < uBinSize; i++)
	{
		iSumC += h_puHist[i];
	}
	printf("\n%%%%%%%%%%%%%% CPU 计算直方图:%%%%%%%%%%%%%%\n");
	printf("序列长度 = %d\n", uArraySize);
	printf("重复次数 = %d\n", iIterNum);
	printf("Hist累计 = %d\n", iSumC / iIterNum);
	printf("平均用时 = %fms\n", dblTimeElps / (Cuda64f)iIterNum);
	printf("%%%%%%%%%%%%%% CPU 计算直方图:%%%%%%%%%%%%%%\n");

使用CUDA 原子操作atomicAdd

__global__ void myhistogram256Kernel_01(const Cuda8u *d_hist_data, Cuda32u *d_bin_data)
{
	const Cuda32u idx = blockIdx.x*blockDim.x + threadIdx.x;
	const Cuda32u idy = blockIdx.y*blockDim.y + threadIdx.y;
	const Cuda32u tid = idx + idy*blockDim.x*gridDim.x;

	const Cuda8u value = d_hist_data[tid];
	atomicAdd(&(d_bin_data[value]), 1);

}
void cudaHist_01(Cuda8u* d_puchData, Cuda32u *d_puHist)
{
	// 总的thread数量要和数组长度相同.
	dim3 thread_rect(16, 16);
	dim3 block_rect(16, 16);
	myhistogram256Kernel_01 << <block_rect, thread_rect >> >(d_puchData, d_puHist);
}

main函数调用:

	// 先将CPU里的数据搬移到GPU中!
	memset((void*)h_puHist, 0, uBinSize*sizeof(Cuda32u));
	Cuda8u * d_puchData = NULL;
	Cuda32u * d_puHist = NULL;
	checkCudaErrors(cudaMalloc((void**)&d_puchData, uArraySize*sizeof(Cuda8u)));
	checkCudaErrors(cudaMalloc((void**)&d_puHist, uBinSize*sizeof(Cuda32u)));
	checkCudaErrors(cudaMemcpy((void*)d_puchData, (void*)h_puchData, uArraySize*sizeof(Cuda8u), cudaMemcpyHostToDevice));
	checkCudaErrors(cudaMemcpy((void*)d_puHist, (void*)h_puHist, uBinSize*sizeof(Cuda32u), cudaMemcpyHostToDevice));
	// 预热
	cudaAdd();
	// 开始计时
	cudaEvent_t start, stop;
	Cuda32f elapsedTime = 0.0;
	cudaEventCreate(&start);
	cudaEventCreate(&stop);
	cudaEventRecord(start, 0);
	for (Cuda32u i = 0; i < iIterNum;i++)
	{
		// 求直方图
		//cudaHist_07((Cuda32u*)d_puchData, d_puHist, N);
		cudaHist_01(d_puchData, d_puHist);
	}
	// 结束计时
	cudaEventRecord(stop, 0);
	cudaEventSynchronize(stop);
	cudaEventElapsedTime(&elapsedTime, start, stop);
	cudaEventDestroy(start);
	cudaEventDestroy(stop);
	// 将GPU内的数据拷回CPU
	checkCudaErrors(cudaMemcpy((void*)h_puHist, (void*)d_puHist, uBinSize*sizeof(Cuda32u), cudaMemcpyDeviceToHost));
	iSumC = 0;
	for (Cuda32u i = 0; i < uBinSize; i++)
	{
		iSumC += h_puHist[i];
	}
	printf("\n%%%%%%%%%%%%%% CUDA 计算直方图:%%%%%%%%%%%%%%\n");
	printf("序列长度 = %d\n", uArraySize);
	printf("重复次数 = %d\n", iIterNum);
	printf("Hist累计 = %d\n", iSumC / iIterNum);
	printf("平均用时 = %f ms\n", elapsedTime / (Cuda32u)iIterNum);
	printf("%%%%%%%%%%%%%% CUDA 计算直方图:%%%%%%%%%%%%%%\n\n");
	// 释放资源
	checkCudaErrors(cudaFree((void*)d_puchData));
	checkCudaErrors(cudaFree((void*)d_puHist));

	cudaDeviceReset();

运行结果:
在这里插入图片描述

以下是个简单的 CUDA 直方图计算代码示例: ```cpp __global__ void histogram_kernel(int* data, int data_size, int* histogram, int histogram_size) { int tid = blockIdx.x * blockDim.x + threadIdx.x; if (tid < data_size) { atomicAdd(&histogram[data[tid]], 1); } } void histogram(int* data, int data_size, int* histogram, int histogram_size) { int* d_data; int* d_histogram; cudaMalloc(&d_data, sizeof(int) * data_size); cudaMemcpy(d_data, data, sizeof(int) * data_size, cudaMemcpyHostToDevice); cudaMalloc(&d_histogram, sizeof(int) * histogram_size); cudaMemset(d_histogram, 0, sizeof(int) * histogram_size); int block_size = 256; int grid_size = (data_size + block_size - 1) / block_size; histogram_kernel<<<grid_size, block_size>>>(d_data, data_size, d_histogram, histogram_size); cudaMemcpy(histogram, d_histogram, sizeof(int) * histogram_size, cudaMemcpyDeviceToHost); cudaFree(d_data); cudaFree(d_histogram); } ``` 这个代码包含两个函数:`histogram` 和 `histogram_kernel`。`histogram_kernel` 是在 GPU 上执行的实际计算。`histogram` 函数分配和释放 GPU 内存,并将数据从主机内存复制到 GPU 内存,然后调用 `histogram_kernel` 函数进行计算,并将结果从 GPU 内存复制回主机内存。 `histogram_kernel` 函数接受四个参数:`data` 指向数据数组的指针,`data_size` 是数据数组的大小,`histogram` 指向直方图数组的指针,`histogram_size` 是直方图数组的大小。该函数计算每个数据元素出现的次数,并使用原子操作将其增加到直方图数组的正确位置。 `histogram` 函数分配和释放两个 GPU 内存数组,并将数据从主机内存复制到 GPU 内存。然后计算需要的线程块和线程数量,调用 `histogram_kernel` 函数执行计算。最后,将计算结果从 GPU 内存复制回主机内存。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值