Search code examples
algorithmcudathrust

How would you implement this function in CUDA? (offsets in sorted integer vector)


I have a sorted integer array on the device, e.g.:

[0,0,0,1,1,2,2]

And I want the offsets to each element in another array:

[0,3,5]

(since the first 0 is at position 0, the first 1 at position 3 and so on) I know how many different elements there will be beforehand. How would you implement this efficiently in CUDA? I'm not asking for code, but a high level description of the algorithm you would implement to compute this transformation. I already hat a look at the various functions in the thrust name space, but could not think of any combination of thrust functions to achieve this. Also, does this transformation have a widely accepted name?


Solution

  • You can solve this in Thrust using thrust::unique_by_key_copy with thrust::counting_iterator. The idea is to treat your integer array as the keys argument to unique_by_key_copy and to use a sequence of ascending integers (i.e., counting_iterator) as the values. unique_by_key_copy will compact the values array into the indices of each unique key:

    #include <thrust/device_vector.h>
    #include <thrust/iterator/counting_iterator.h>
    #include <thrust/iterator/discard_iterator.h>
    #include <thrust/unique.h>
    #include <thrust/copy.h>
    #include <iterator>
    #include <iostream>
    
    int main()
    {
      thrust::device_vector<int> keys(7);
      keys[0] = 0; keys[1] = 0; keys[2] = 0;
      keys[3] = 1; keys[4] = 1; keys[5] = 2; keys[6] = 2;
    
      std::cout << "keys before unique_by_key_copy: [ ";
      thrust::copy(keys.begin(), keys.end(), std::ostream_iterator<int>(std::cout," "));
      std::cout << "]" << std::endl;
    
      thrust::device_vector<int> offsets(3);
    
      thrust::unique_by_key_copy(keys.begin(), keys.end(),          // keys
                                 thrust::make_counting_iterator(0), // [0, 1, 2, 3, ...] are the values
                                 thrust::make_discard_iterator(),   // discard the compacted keys
                                 offsets.begin());                  // the offsets are the values
    
      std::cout << "offsets after unique_by_key_copy: [ ";
      thrust::copy(offsets.begin(), offsets.end(), std::ostream_iterator<int>(std::cout," "));
      std::cout << "]" << std::endl;
    
      return 0;
    }
    

    Here's the output:

    $ nvcc test.cu -run
    keys before unique_by_key_copy: [ 0 0 0 1 1 2 2 ]
    offsets after unique_by_key_copy: [ 0 3 5 ]