Optimization 加速CUDA多仓/少仓原子计算

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

我正在尝试优化我在CUDA中的直方图计算。与相应的OpenMP CPU计算相比,它给了我极好的加速。然而,我怀疑(与直觉一致)大多数像素会落入几个桶中。为了便于讨论,假设有256个像素落入两个桶中

做这件事最简单的方法就是按照

  • 将变量加载到共享内存中
    • 如果需要,对无符号字符等进行矢量化加载
  • 在共享内存中执行原子外接程序吗
  • 对全局执行合并写入
  • 大概是这样的:

    __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
    }