使用CUDA查找数组中未知大小的区域的最大值

时间:2014-09-01 11:57:22

标签: c++ c arrays cuda thrust

假设我有一个包含所有不同数字A[4000]

的数组[45,21,764,234,7,0,12,55,...]

然后我有另一个数组B[4000],表示数组A中区域的位置,如果它是区域的一部分,则为1,如果0,则为1's它不是。如果0彼此相邻,这意味着它们属于同一地区,如果它们不是彼此相邻(1's之间有B = [1,1,1,0,1,1,0,0...]),那么是不同地区的一部分。

离。 first three numbers in array A表示我想要找到5th and 6th numbers in array A, etc.区域中的最大值,以及C[4000]中的最大值 这样我就可以生成一个数组A,在B表示的每个区域中保存0的最大值,而在不属于的区域中保存C = [764,764,764,0,7,7,0,0...]区域。

所以在这种情况下0 to 2,000 regions

2 to 4,000 numbers long可以有任何地方,区域的长度可以从4000开始。我事先不知道有多少地区或地区的大小不同。

我一直试图在CUDA中提出一个可以实现这个结果的内核。它需要尽可能快地完成,因为它实际上它将用于图像,这只是一个简化的例子。我的所有想法,例如使用缩减,只有在只有一个区域跨越所有A个数组1的情况下才有效。但是,我不认为我可以在这里使用缩减,因为阵列中可能有多个区域被3996分隔为0's个空格(int intR = 0; while(B[blockIdx.x * blockDim.x + threadIdx.x + intR] > 0){ intMaxR = intMaxR < A[blockIdx.x * blockDim.x + threadIdx.x + intR] ? A[blockIdx.x * blockDim.x + threadIdx.x + intR] : intMaxR; intR++; } int intL = 0; while(B[blockIdx.x * blockDim.x + threadIdx.x - intL] > 0){ intMaxL = intMaxL < A[blockIdx.x * blockDim.x + threadIdx.x - intL] ? A[blockIdx.x * blockDim.x + threadIdx.x + intL] : intMaxL; intL++; } intMax = intMaxR > intMaxL ? intMaxR : intMaxL; for(int i = 0; i < intR; i++){ C[blockIdx.x * blockDim.x + threadIdx.x + i] = intMax; } for(int i = 0; i < intL; i++){ C[blockIdx.x * blockDim.x + threadIdx.x - i] = intMax; } ),并且缩减将导致我松散轨道分开的地区。或者,内核中有太多的循环,而if中的语句要快,例如

{{1}}

显然,即使使用共享内存,代码也很慢,并且并没有真正利用CUDA的并行特性。有没有人知道如何在CUDA中有效地完成这项工作?

提前致谢。

1 个答案:

答案 0 :(得分:2)

一种可能的方法是使用thrust

可能的顺序是这样的:

  1. 使用 thrust::reduce_by_key 生成每个范围的最大值。
  2. 使用thrust :: adjacent_difference来描绘每个范围的开始
  3. 对步骤2的结果使用包容性扫描以生成聚集索引,即将用于选择将在输出向量的每个位置中的缩减值(步骤1的结果)的索引。
  4. 使用thrust::gather_if使用步骤3中生成的聚集索引,有选择地将缩小值放入输出向量中的适当位置(B向量中有1)。
  5. 这是一个完整工作的代码,使用A和B向量来证明这一点:

    #include <iostream>
    #include <thrust/device_vector.h>
    #include <thrust/adjacent_difference.h>
    #include <thrust/reduce.h>
    #include <thrust/copy.h>
    #include <thrust/transform_scan.h>
    #include <thrust/iterator/discard_iterator.h>
    #include <thrust/iterator/transform_iterator.h>
    #include <thrust/functional.h>
    
    #define DSIZE 8
    
    template <typename T>
    struct abs_val : public thrust::unary_function<T, T>
    {
      __host__ __device__
      T operator()(const T& x) const
      {
        if (x<0) return -x;
        else return x;
      }
    };
    
    template <typename T>
    struct subtr : public thrust::unary_function<T, T>
    {
      const T val;
      subtr(T _val): val(_val) {}
      __host__ __device__
      T operator()(const T& x) const
      {
        return  x-val;
      }
    };
    
    int main(){
    
      int A[DSIZE] = {45,21,764,234,7,0,12,55};
      int B[DSIZE] = {1,1,1,0,1,1,0,0};
      thrust::device_vector<int> dA(A, A+DSIZE);
      thrust::device_vector<int> dB(B, B+DSIZE);
      thrust::device_vector<int> dRed(DSIZE);
      thrust::device_vector<int> diffB(DSIZE);
      thrust::device_vector<int> dRes(DSIZE);
    
      thrust::reduce_by_key(dB.begin(), dB.end(), dA.begin(), thrust::make_discard_iterator(), dRed.begin(), thrust::equal_to<int>(), thrust::maximum<int>());
      thrust::adjacent_difference(dB.begin(), dB.end(), diffB.begin());
      thrust::transform_inclusive_scan(diffB.begin(), diffB.end(), diffB.begin(), abs_val<int>(), thrust::plus<int>());
      thrust::gather_if(thrust::make_transform_iterator(diffB.begin(), subtr<int>(B[0])), thrust::make_transform_iterator(diffB.end(), subtr<int>(B[0])), dB.begin(), dRed.begin(), dRes.begin());
      thrust::copy(dRes.begin(), dRes.end(), std::ostream_iterator<int>(std::cout, " "));
      std::cout  << std::endl;
      return 0;
    }
    

    关于示例的说明:

    1. reduce_by_key为每个产生减少的值(最大值) B中连续的0序列 1序列。你真的需要 1个序列的最大值。我们将丢弃0序列 通过gather_if函数的最大值。
    2. 我考虑到B矢量可能从两者开始的可能性 通过transform_iterator使用1序列或0序列 处理步骤2的矢量结果,减去第一个 来自每个聚集指数的B向量的值。
    3. adjacent_difference操作将产生1或-1到 描绘新序列的开始。我用 transform_inclusive_scan变量与abs_val仿函数一样,用于扫描目的(即生成聚集索引)。
    4. 以上代码应生成与您所需的C输出向量匹配的结果,如下所示:

      $ nvcc -arch=sm_20 -o t53 t53.cu
      $ ./t53
      764 764 764 0 7 7 0 0
      $
      
    5. 我们可以使用thrust::placeholders进一步简化上述代码,无需额外的仿函数定义:

      #include <iostream>
      #include <thrust/device_vector.h>
      #include <thrust/adjacent_difference.h>
      #include <thrust/reduce.h>
      #include <thrust/copy.h>
      #include <thrust/transform_scan.h>
      #include <thrust/iterator/discard_iterator.h>
      #include <thrust/iterator/transform_iterator.h>
      #include <thrust/functional.h>
      
      #define DSIZE 2000000
      using namespace thrust::placeholders;
      
      typedef int mytype;
      
      int main(){
      
        mytype *A = (mytype *)malloc(DSIZE*sizeof(mytype));
        int *B = (int *)malloc(DSIZE*sizeof(int));
        for (int i = 0; i < DSIZE; i++){
          A[i] = (rand()/(float)RAND_MAX)*10.0f;
          B[i] = rand()%2;}
        thrust::device_vector<mytype> dA(A, A+DSIZE);
        thrust::device_vector<int> dB(B, B+DSIZE);
        thrust::device_vector<mytype> dRed(DSIZE);
        thrust::device_vector<int> diffB(DSIZE);
        thrust::device_vector<mytype> dRes(DSIZE);
      
        cudaEvent_t start, stop;
        cudaEventCreate(&start);
        cudaEventCreate(&stop);
        cudaEventRecord(start);
        thrust::reduce_by_key(dB.begin(), dB.end(), dA.begin(), thrust::make_discard_iterator(), dRed.begin(), thrust::equal_to<mytype>(), thrust::maximum<mytype>());
        thrust::adjacent_difference(dB.begin(), dB.end(), diffB.begin());
        thrust::transform_inclusive_scan(diffB.begin(), diffB.end(), diffB.begin(), _1*_1, thrust::plus<int>());
        thrust::gather_if(thrust::make_transform_iterator(diffB.begin(), _1 - B[0]), thrust::make_transform_iterator(diffB.end(), _1 - B[0]), dB.begin(), dRed.begin(), dRes.begin());
        cudaEventRecord(stop);
        cudaEventSynchronize(stop);
        float et;
        cudaEventElapsedTime(&et, start, stop);
        std::cout<< "elapsed time: " << et << "ms " << std::endl;
        thrust::copy(dRes.begin(), dRes.begin()+10, std::ostream_iterator<mytype>(std::cout, " "));
        std::cout  << std::endl;
        return 0;
      }
      

      (我已修改上述占位符代码,还包括生成更大尺寸的数据集,以及一些基本的计时设备。)