【问题标题】:How to use Thrust to implement reduce by key when keys are strings or char array当键为字符串或字符数组时,如何使用 Thrust 实现按键归约
【发布时间】:2016-03-23 10:26:23
【问题描述】:

输入:

公元前
BD
卑诗省
卑诗省
BD
光盘

输出:

公元前 3 年
BD 2
CD 1

如果我使用 char 类型作为键,它是可用的。但似乎 Thrust 不支持字符串作为键。

#include <thrust/device_vector.h>
#include <thrust/iterator/constant_iterator.h>
#include <thrust/reduce.h>
#include <string>

int main(void)
{
  std::string data = "aaabbbbbcddeeeeeeeeeff";

  size_t N = data.size();

  thrust::device_vector<char> input(data.begin(), data.end());

  thrust::device_vector<char> output(N);
  thrust::device_vector<int>  lengths(N);

  size_t num_runs =
    thrust::reduce_by_key(input.begin(), input.end(),        
                      thrust::constant_iterator<int>(1), 
                      output.begin(),                    
                      lengths.begin()                    
                      ).first - output.begin();
   return 0;
}

如何使用 Thrust 实现?

【问题讨论】:

    标签: cuda thrust


    【解决方案1】:

    向@AngryLettuce 道歉,这里有两种可能的方法:

    方法一:

    1. 创建一个结构来保存您的密钥。该结构将为密钥中的每个字符包含一个 char 项。
    2. sort 将相似的键组合在一起的键。看来您想要的实际上只是每种键类型的计数,无论它出现在序列中的哪个位置。为了通过reduce_by_key 促进这一点,有必要首先将类似的键组合在一起。否则,reduce_by_key 会将由不同的中间键分隔的相似键视为不同的键序列。从您想要的输入和输出中可以明显看出这不是您想要的。
    3. 现在对已排序的键使用 reduce_by_key,以计算相似键。

    第 2 步需要(对于此方法)一个仿函数来对键进行排序,而第 3 步需要一个仿函数来识别“相等”键的含义,这是reduce_by_key 需要的。

    方法二:

    1. 创建两个独立的chardevice_vector(s),一个保存每个键的第一个字母,另一个保存每个键的第二个字母。然后,我们将在代码的其余部分使用zip_iterator,将这两个向量视为一个统一的“关键”向量。

    2. sort 压缩的密钥向量。在这种情况下,thrust 知道如何对基本类型的压缩向量进行排序,并且不需要单独的排序函子

    3. 对压缩(和排序)的密钥向量执行reduce_by_key。这再一次不需要单独的相等函子。 Thrust 知道如何确定基本类型的压缩向量是否相等。

    第二种方法除了不需要任何函子定义外,可能还会更快,因为与第一种方法中的 AoS(结构数组)相比,zip_iterator 倾向于改进数据访问。

    这是一个演示这两种方法的工作示例:

    $ cat t1004.cu
    #include <thrust/device_vector.h>
    #include <thrust/sort.h>
    #include <thrust/reduce.h>
    #include <thrust/iterator/constant_iterator.h>
    #include <iostream>
    
    #include <thrust/iterator/zip_iterator.h>
    
    struct key {
      char k1;
      char k2;
    };
    
    struct sort_functor{
      __host__ __device__ bool operator()(key &k1, key &k2){
        if (k1.k1 < k2.k1) return true;
        if (k1.k1 > k2.k1) return false;
        if (k1.k2 < k2.k2) return true;
        return false;}
    };
    
    struct equal_key{
      __host__ __device__ bool operator()(key k1, key k2){
        if ((k1.k1 == k2.k1)&&(k1.k2 == k2.k2)) return true;
        return false;}
    };
    
    int main(){
    
      key data[] = {{'B','C'},{'B','D'},{'B','C'},{'B','C'},{'B','D'},{'C','D'}};;
      size_t dsize = sizeof(data)/sizeof(key);
    
    
    //method 1
      thrust::device_vector<key> keys(data, data+dsize);
      thrust::device_vector<key> keys_out(dsize);
      thrust::device_vector<int> lengths(dsize);
      thrust::sort(keys.begin(), keys.end(), sort_functor());
      int rsize = thrust::reduce_by_key(keys.begin(), keys.end(), thrust::constant_iterator<int>(1), keys_out.begin(), lengths.begin(),equal_key()).first - keys_out.begin();
      std::cout << "Method1:" << std::endl;
      for (int i = 0; i < rsize; i++){
        key temp = keys_out[i];
        int len = lengths[i];
        std::cout << " " << temp.k1 << temp.k2 << " " <<  len << std::endl;}
    
    //method 2
    
      //get the key data into 2 separate vectors.
      //there are more efficient ways to do this
      //but this is not the crux of your question
    
      thrust::device_vector<char> k1;
      thrust::device_vector<char> k2;
      for (int i = 0; i < dsize; i++){
        k1.push_back(data[i].k1);
        k2.push_back(data[i].k2);}
    
      thrust::sort(thrust::make_zip_iterator(thrust::make_tuple(k1.begin(), k2.begin())), thrust::make_zip_iterator(thrust::make_tuple(k1.end(), k2.end())));
    
      thrust::device_vector<char> k1r(dsize);
      thrust::device_vector<char> k2r(dsize);
      rsize = thrust::reduce_by_key(thrust::make_zip_iterator(thrust::make_tuple(k1.begin(), k2.begin())), thrust::make_zip_iterator(thrust::make_tuple(k1.end(), k2.end())), thrust::constant_iterator<int>(1), thrust::make_zip_iterator(thrust::make_tuple(k1r.begin(), k2r.begin())), lengths.begin()).first - thrust::make_zip_iterator(thrust::make_tuple(k1r.begin(),k2r.begin()));
      std::cout << "Method2:" << std::endl;
      for (int i = 0; i < rsize; i++){
        char c1 = k1r[i];
        char c2 = k2r[i];
        int len = lengths[i];
        std::cout << " " << c1 << c2 << " " <<  len << std::endl;}
    
      return 0;
    }
    $ nvcc -o t1004 t1004.cu
    $ ./t1004
    Method1:
     BC 3
     BD 2
     CD 1
    Method2:
     BC 3
     BD 2
     CD 1
    $
    

    这是方法 2 的改进版本。您应该能够直接使用字符串/字符数组,并且该版本也可以相当容易地修改以适应 2 到 10 个字符的密钥长度。此方法使用strided range iterator 直接从数据数组中提取各个关键字符:

    $ cat t1004.cu
    #include <thrust/device_vector.h>
    #include <thrust/sort.h>
    #include <thrust/reduce.h>
    #include <thrust/iterator/constant_iterator.h>
    #include <iostream>
    
    #include <thrust/iterator/zip_iterator.h>
    #include <thrust/iterator/transform_iterator.h>
    #include <thrust/iterator/permutation_iterator.h>
    
    template <typename Iterator>
    class strided_range
    {
        public:
    
        typedef typename thrust::iterator_difference<Iterator>::type difference_type;
    
        struct stride_functor : public thrust::unary_function<difference_type,difference_type>
        {
            difference_type stride;
    
            stride_functor(difference_type stride)
                : stride(stride) {}
    
            __host__ __device__
            difference_type operator()(const difference_type& i) const
            {
                return stride * i;
            }
        };
    
        typedef typename thrust::counting_iterator<difference_type>                   CountingIterator;
        typedef typename thrust::transform_iterator<stride_functor, CountingIterator> TransformIterator;
        typedef typename thrust::permutation_iterator<Iterator,TransformIterator>     PermutationIterator;
    
        // type of the strided_range iterator
        typedef PermutationIterator iterator;
    
        // construct strided_range for the range [first,last)
        strided_range(Iterator first, Iterator last, difference_type stride)
            : first(first), last(last), stride(stride) {}
    
        iterator begin(void) const
        {
            return PermutationIterator(first, TransformIterator(CountingIterator(0), stride_functor(stride)));
        }
    
        iterator end(void) const
        {
            return begin() + ((last - first) + (stride - 1)) / stride;
        }
    
        protected:
        Iterator first;
        Iterator last;
        difference_type stride;
    };
    
    typedef thrust::device_vector<char>::iterator cIterator;
    
    int main(){
    
    //method 2
    
      //get the key data into separate vectors, one per character in key.
    #define KEYLEN 2
      const char data[] = "BCBDBCBCBDCD";
      size_t dsize = sizeof(data)/sizeof(char);
      size_t numkeys = dsize/KEYLEN;
      thrust::device_vector<char> keys(data, data+dsize);
      strided_range<cIterator>  *str_k[KEYLEN];
      for (int i = 0; i < KEYLEN; i++)
        str_k[i] = new strided_range<cIterator>(keys.begin()+i, keys.end(), KEYLEN);
    
    //modify this line also if KEYLEN changes (max 10)
      auto my_z = thrust::make_zip_iterator(thrust::make_tuple((*str_k[0]).begin(), (*str_k[1]).begin()));
    
      thrust::sort(my_z, my_z+numkeys);
    
      thrust::device_vector<char> kr[KEYLEN];
      for (int i = 0; i < KEYLEN; i++)
        kr[i].resize(numkeys);
    
    //modify this line also if KEYLEN changes (max 10)
      auto my_zr = thrust::make_zip_iterator(thrust::make_tuple(kr[0].begin(), kr[1].begin()));
    
      thrust::device_vector<int> lengths(numkeys);
    
      size_t rsize = thrust::reduce_by_key(my_z, my_z + numkeys, thrust::constant_iterator<int>(1), my_zr, lengths.begin()).first - my_zr;
      std::cout << "Method2:" << std::endl;
    
      for (int i = 0; i < rsize; i++){
        std::cout << " ";
        for (int j = 0; j < KEYLEN; j++){
          char c = kr[j][i];
          std::cout << c; }
        int len = lengths[i];
        std::cout <<" " <<  len << std::endl;}
    
      return 0;
    }
    $ nvcc -std=c++11 t1004.cu -o t1004
    $ ./t1004
    Method2:
     BC 3
     BD 2
     CD 1
    $
    

    【讨论】:

    • 感谢您的回复!为了清楚地解释我的问题,我简化了输入和问题,(我的导师)不允许发布真正的代码,所以我选择了一个推力邮件列表的例子......我没想到会惹恼这个生气的生菜家伙。 ..现在他真的很生气。再次感谢你,非常感谢。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-02-14
    • 1970-01-01
    • 1970-01-01
    • 2011-09-28
    • 2013-10-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多