Optimization 加速CUDA多仓/少仓原子计算
我正在尝试优化我在CUDA中的直方图计算。与相应的OpenMP CPU计算相比,它给了我极好的加速。然而,我怀疑(与直觉一致)大多数像素会落入几个桶中。为了便于讨论,假设有256个像素落入两个桶中 做这件事最简单的方法就是按照Optimization 加速CUDA多仓/少仓原子计算,optimization,cuda,histogram,binning,gpu-atomics,Optimization,Cuda,Histogram,Binning,Gpu Atomics,我正在尝试优化我在CUDA中的直方图计算。与相应的OpenMP CPU计算相比,它给了我极好的加速。然而,我怀疑(与直觉一致)大多数像素会落入几个桶中。为了便于讨论,假设有256个像素落入两个桶中 做这件事最简单的方法就是按照 将变量加载到共享内存中 如果需要,对无符号字符等进行矢量化加载 在共享内存中执行原子外接程序吗 对全局执行合并写入 大概是这样的: __global__ void shmem_atomics_reducer(int *data, int *count){ uint
- 如果需要,对无符号字符等进行矢量化加载
__global__ void shmem_atomics_reducer(int *data, int *count){
uint tid = blockIdx.x*blockDim.x + threadIdx.x;
__shared__ int block_reduced[NUM_THREADS_PER_BLOCK];
block_reduced[threadIdx.x] = 0;
__syncthreads();
atomicAdd(&block_reduced[data[tid]],1);
__syncthreads();
for(int i=threadIdx.x; i<NUM_BINS; i+=NUM_BINS)
atomicAdd(&count[i],block_reduced[i]);
}
\uuuuuu全局\uuuuuu无效shmem\u原子减速机(int*数据,int*计数){
uint tid=块IDX.x*块尺寸x+线程IDX.x;
__共享的线程块减少[每个线程块的线程数];
block_reduced[threadIdx.x]=0;
__同步线程();
原子添加(&block_reduced[data[tid]],1);
__同步线程();
对于(inti=threadIdx.x;i>5;
//需要车道ID,因为我们将进入扭曲层
uint lane_id=threadIdx.x%32;
//投票
uint-warp\u-set\u位=0;
//存储扭曲级别和
__共享扭曲减少的扭曲计数[每个块的扭曲数量];
//共享数据
__共享单元s_数据[NUM_THREADS_PER_BLOCK];
//加载共享数据-可以存储到寄存器
s_数据[threadIdx.x]=数据[tid];
__同步线程();
//可疑循环-我认为我们需要更多的并行性
对于(int i=0;i0;j>>=1){
如果(t我在使用集群时遇到了类似的挑战,但最终,最好的解决方案是使用扫描模式对处理进行分组。因此,我认为这不适合你。既然你要求在这方面有一些经验,我将与你分享我的经验
问题
在您的第1个代码中,我猜处理低性能和减少存储箱数量与warp stall有关,因为您对每个评估数据执行的处理很少。当存储箱数量增加时,处理与全局内存负载(数据信息)之间的关系对于该内核也增加了。您可以在Nsight的性能分析中通过“问题效率”实验很容易地检查这一点。可能您在至少有一个可供使用的偏差(偏差问题效率)的情况下获得了较低的周期率
由于我无法将可删除扭曲的数量提高到接近95%,因此我放弃了这种方法,因为在某些情况下它会变得更糟(内存依赖性暂停了90%的处理周期)。
洗牌和投票减少在箱子数量不太大的情况下非常有用。如果箱子数量太大,则每个箱子过滤器都应该有少量线程处于活动状态。因此,您可能最终会出现大量代码分歧,这对于并行处理来说是非常不可取的。您可以尝试对分歧进行分组,以消除分支,并使其保持一致一个好的控制流程,因此整个扭曲/块呈现类似的处理,但跨块的几率很大
可行的解决方案
我不知道在哪里,但我看到有很多很好的解决方案,你试过了吗
您也可以使用a并尝试类似的操作,但我不确定它会在多大程度上提高您的性能:
__global__ hist(int4 *data, int *count, int N, int rem, unsigned int init) {
__shared__ unsigned int sBins[N_OF_BINS]; // you may want to declare this one dinamically
int idx = blockIdx.x * blockDim.x + threadIdx.x;
if (threadIdx.x < N_OF_BINS) sBins[threadIdx.x] = 0;
for (int i = 0; i < N; i+= warpSize) {
atomicAdd(&sBins[data[i + init].w], 1);
atomicAdd(&sBins[data[i + init].x], 1);
atomicAdd(&sBins[data[i + init].y], 1);
atomicAdd(&sBins[data[i + init].z], 1);
}
//process remaining elements if the data is not multiple of 4
// using recast and a additional control
for (int i = 0; i < rem; i++) {
atomicAdd(&sBins[reinterpret_cast<int*>(data)[N * 4 + init + i]], 1);
}
//update your histogram data here
}
\uuuuu全局\uuuuuuhist(int4*数据、int*计数、int N、int rem、无符号int init){
__shared_uuuunsigned int sBins[N_OF_bin];//您可能希望以友好方式声明此项
int idx=blockIdx.x*blockDim.x+threadIdx.x;
如果(threadIdx.x
就你的问题而言,它可能应该关闭,因为“不清楚你在问什么”——你在某些地方有点含糊不清,特别是关于你的问题的确切限制,你希望在“让我们说”中发生什么举例等等。另外,你基本上是在征求意见,而不是一个问题的具体答案,这是结束讨论的另一个原因。然而,我个人现在正在做几乎相同的事情,所以我有偏见。无论如何,我实际上想在场外提供我的意见。如果你想进一步讨论,我已经创建了一个对话。我正在采取行动当输入中存在大量简并时,我在柱状图和原子计算的风格指南中寻找一些东西。很高兴讨论。经过近三年的时间,我学习了更多的机器学习,我决定问题不在于实现算法(我们使用互信息,其中联合直方图必须使用原子),但算法本身。所以我认为我们可以考虑这个问题“解决”为我们的目的。没有治愈原子不安。矢量化的负载不能改善性能在开普勒拱(回来时,我测试它),但不错的技巧剖析。
__global__ hist(int4 *data, int *count, int N, int rem, unsigned int init) {
__shared__ unsigned int sBins[N_OF_BINS]; // you may want to declare this one dinamically
int idx = blockIdx.x * blockDim.x + threadIdx.x;
if (threadIdx.x < N_OF_BINS) sBins[threadIdx.x] = 0;
for (int i = 0; i < N; i+= warpSize) {
atomicAdd(&sBins[data[i + init].w], 1);
atomicAdd(&sBins[data[i + init].x], 1);
atomicAdd(&sBins[data[i + init].y], 1);
atomicAdd(&sBins[data[i + init].z], 1);
}
//process remaining elements if the data is not multiple of 4
// using recast and a additional control
for (int i = 0; i < rem; i++) {
atomicAdd(&sBins[reinterpret_cast<int*>(data)[N * 4 + init + i]], 1);
}
//update your histogram data here
}