对CUDA /推力中的分段数据进行成对运算

时间:2015-03-18 13:32:42

标签: c++ cuda gpgpu thrust

假设我有

  • 数据数组,
  • 包含引用数据数组中的条目和
  • 的键的数组
  • 第三个数组,其中包含每个键数组条目的 id

e.g。

DataType dataArray[5];
int keyArray[10] = {1, 2, 3, 1, 2, 2, 1, 1, 1, 1};
int ids[10]      = {0, 0, 0, 1, 2, 2, 2, 3, 3, 3};

如何使用推力忽略案例ResultDataType fun(int key1, int key2, int id),为每个ID段成对执行自定义运算符key1 == key2

在这个例子中,我想执行并存储结果:

fun(1,2,0)
fun(1,3,0)
fun(2,3,0)
fun(2,1,2)

2 个答案:

答案 0 :(得分:4)

事实证明这比我最初的想法更为复杂。我的解释是,你基本上想要计算每次采取两个N个事物的所有组合,而不是重复,每个段(每个段的N是不同的)。我将提供一个我认为涵盖您可能想要考虑的大多数概念的答案。当然,这只是一种可能的解决方法。

这可能无法达到您想要的解决方案(尽管我认为它非常接近)。我的目的不是为您提供完成的代码,而是为了演示可能的算法方法。

以下是该算法的演练:

  1. 按段对键进行排序。如果您可以保证类似的键(在一个段内)被组合在一起,则不需要此步骤。 (您提供的数据不需要此步骤。)我们可以使用背靠背stable_sort_by_key来完成此任务。输出:

    d_keys:
    1,2,3,1,1,2,2,1,1,1,
    d_segs:
    0,0,0,1,2,2,2,3,3,3,
    
  2. 将每个段的数据集简化为唯一键(删除重复的键。)我们可以在键和段上与thrust::unique_copy一起执行此操作,并使用适当的函数来定义键的相等性只在一个细分中。输出:

    d_ukeys:
    1,2,3,1,1,2,1,
    d_usegs:
    0,0,0,1,2,2,3,
    
  3. 现在计算删除重复键时每个段的长度。我们可以使用thrust::reduce_by_keyconstant_iterator执行此操作。输出:

    d_seg_lens:
    3,1,2,1,
    
  4. 现在我们知道每个段长度,我们想要删除段长度为1的任何段(加上它的关联键)。这些段没有任何可能的键对。为了实现这一点,我们还需要计算每个段的起始索引并创建一个模板来标识长度为1的段,然后我们可以使用remove_ifscatter_if来删除相关的段和键数据。输出:

    d_seg_idxs:
    0,3,4,6,
    d_stencil:
    0,0,0,1,0,0,1,
    

    和减少的数据:

    d_ukeys:
    1,2,3,1,2,
    d_usegs:
    0,0,0,2,2,
    d_seg_lens:
    3,2,
    
  5. 此时我们的目标是创建一个适当长度的向量,以便它可以为每次采用两个N个事物的每个组合保留一个条目,其中N是每个段的长度。因此,对于上面的第一部分,它有3个项目,因此一次2个采取的3件事的组合需要存储3个组合总计。同样,长度2以上的第二段在2个键之间仅具有一个唯一组合,因此对于该段仅一个组合需要存储。我们可以使用标准组合公式计算此存储长度,该公式减少为一次采取2个N项的组合:

      C = (N)*(N-1)/2
    

    我们将使用此公式,与我们的段长度一起传递给thrust::transform_reduce,以计算所需的总存储长度。 (对于此示例,总共4个组合)。

  6. 一旦我们确定了总长度,并分配了该长度的必要向量,我们需要生成属于每个段的实际组合。这又是一个多步骤序列,首先生成标记以标记(描绘)这些向量中的每个“段”。使用flag数组,我们可以使用exclusive_scan_by_key生成一个序数序列,用于标识每个段中每个位置所属的组合。输出:

    d_flags:
    1,0,0,1,
    example ordinal sequence:
    0,1,2,0
    
  7. 对于每个段的序数序列,我们现在需要为每个段生成实际的唯一组合。这个步骤需要考虑尝试并提出一种算法来在固定的时间内完成这个(即没有迭代)。我想出的算法是将每个序数序列映射到一个矩阵,因此对于一个有4种组合的片段(一次可以有4个东西,所以总共6个组合,大于本例中的任何组合):

        1   2   3 
    
     0  C1  C2  C3 
     1  C4  C5  C6
     2
    

    然后,我们将在主对角线下面的任何组合(上面Cx)“重新映射”到矩阵中的其他位置,如下所示:

        1   2   3 
    
     0  C1  C2  C3  
     1      C5  C6
     2          C4
    
    然后,我们可以读取唯一的组合对作为上述特制矩阵的行和列索引。因此C1对应于逻辑密钥0和逻辑密钥1的组合.C6对应于逻辑密钥1和逻辑密钥3的组合。该矩阵组件和映射到行和列“索引”以产生唯一组合由{处理{1}}仿函数传递给comb_n,它也接收段长度和输入段序数序列,并生成“逻辑”键1和2作为输出:

    thrust::for_each_n

    补充说明:我相信我在这里描述的方法与this question/answer中讨论的方法相当,我最近偶然发现,尽管它们乍一看似乎有所不同。)

  8. 最后一个转换步骤是将逻辑键和段转换为“实际”键和段。输出:

    d_lkey1:
    1,2,2,1,
    d_lkey2:
    0,0,1,0,
    
  9. 这是一个实现上述功能的完整代码。它可能无法完全按照您的要求进行操作,但我认为它与您所描述的非常接近,如果您希望以推力执行此操作,希望这对于创意有用:

    d_key1:
    2,3,3,2,
    d_key2:
    1,1,2,1,
    d_aseg:
    0,0,0,2,
    

答案 1 :(得分:1)

我找到了你的问题的解决方案。我使用了cuda内核和推力原语的混合。我想你的运算符在keys参数上有可交换的属性,即

fun(key1, key2, id)  == fun(key2, key1, id)

我的解决方案分两步生成三个输出数组(keys1,keys2和id)。在第一步中,它只计算输出数组中的元素数,第二步是填充数组。 基本上算法运行两次:第一次“模拟”写入,第二次实际写入输出。 这是我在输出大小取决于输入数据时使用的常见模式。

以下是步骤:

  1. 按段对键进行排序。这一步与@Robert Crovella的算法相同。
  2. 计算每个密钥生成的对数。每个线程都与一个密钥相关联。每个线程开始计算从其基本索引到段末尾的所有有效对。此步骤的输出位于向量d_cpairs
  3. 计算keys1,keys2和id的大小以及这些数组中每对int的偏移量.d_cpairs的总和减少计算输出数组的大小。在d_cpairs上执行的exlusive-scan操作产生输出数组中的对的位置。此步骤的输出位于向量d_addrs
  4. 用数据填充keys1,keys2和id。此步骤与步骤3)完全相同,但它实际上将数据写入keys1,keys2和id。
  5. 以下是我算法的输出:

        d_keys: 1 2 3 1 1 2 2 1 1 1 
        d_segs: 0 0 0 1 2 2 2 3 3 3 
        d_cpairs: 2 1 0 0 1 0 0 0 0 0 
        num_pairs: 4
        d_addrs: 0 2 3 3 3 4 4 4 4 4 
        keys1: 1 1 2 1 
        keys2: 2 3 3 2 
        ids: 0 0 0 2
    

    请注意,输出中的最后一列并不完全符合您的要求,但是如果可交换属性保持不变。

    这是我的完整代码。可能这不是最快的解决方案,但我认为这很简单。

    #include <iostream>
    #include <thrust/device_vector.h>
    #include <thrust/host_vector.h>
    #include <thrust/scan.h>
    #include <thrust/sort.h>
    
    #define SHOW_VECTOR(V,size)  std::cout << #V << ": "; for(int i =0 ; i < size; i++ ){ std::cout << V[i] << " "; } std::cout << std::endl;
    #define RAW_CAST(V) thrust::raw_pointer_cast(V.data())
    
    __global__ 
    void count_kernel(const int *d_keys,const int *d_segs,int *d_cpairs, int siz){
        int tidx = threadIdx.x+ blockIdx.x*blockDim.x;
    
        if(tidx < siz){
            int sum = 0;
            int i = tidx+1; 
            while(d_segs[i] == d_segs[tidx]){        
                if(d_keys[i] != d_keys[tidx] &&
                   d_keys[i] != d_keys[i-1]){
                    sum++;
                }
                i++;
            }
            d_cpairs[tidx] = sum;
        }
    }
    
    __global__ 
    void scatter_kernel(const int *d_keys,
                        const int *d_segs,
                        const int *d_addrs, 
                        int *d_keys1,
                        int *d_keys2,
                        int *d_ids,                           
                        int siz){
        int tidx = threadIdx.x+ blockIdx.x*blockDim.x;
    
        if(tidx < siz){
            int base_address = d_addrs[tidx];
            int j =0;
            int i = tidx+1; 
            while(d_segs[i] == d_segs[tidx]){        
                if(d_keys[i] != d_keys[tidx] &&
                   d_keys[i] != d_keys[i-1]){
    
                   d_keys1[base_address+j] = d_keys[tidx];
                   d_keys2[base_address+j] = d_keys[i];
                   d_ids[base_address+j] = d_segs[i];                      
                   j++;
                }
                i++;
            }
        }
    }
    
    int main(){
    
        int keyArray[] = {1, 2, 3, 1, 2, 2, 1, 1, 1, 1};
        int idsArray[]      = {0, 0, 0, 1, 2, 2, 2, 3, 3, 3};
    
    
        int sz1 = sizeof(keyArray)/sizeof(keyArray[0]);
    
        thrust::host_vector<int> h_keys(keyArray, keyArray+sz1);
        thrust::host_vector<int> h_segs(idsArray, idsArray+sz1);
        thrust::device_vector<int> d_keys = h_keys;
        thrust::device_vector<int> d_segs = h_segs;
        thrust::device_vector<int> d_cpairs(sz1);
        thrust::device_vector<int> d_addrs(sz1);
    
        //sort each segment to group like keys together
        thrust::stable_sort_by_key(d_keys.begin(), d_keys.end(), d_segs.begin());
        thrust::stable_sort_by_key(d_segs.begin(), d_segs.end(), d_keys.begin());
    
        SHOW_VECTOR(d_keys,sz1);
        SHOW_VECTOR(d_segs,sz1);
    
        //count the number of pairs produced by each key
        count_kernel<<<1,sz1>>>(RAW_CAST(d_keys),RAW_CAST(d_segs),RAW_CAST(d_cpairs),sz1);
    
        SHOW_VECTOR(d_cpairs,sz1);
    
        //determine the total number of pairs
        int num_pairs  = thrust::reduce(d_cpairs.begin(),d_cpairs.end());
    
        std::cout << "num_pairs: " << num_pairs << std::endl;
        //compute the addresses     
        thrust::exclusive_scan(d_cpairs.begin(),d_cpairs.end(),d_addrs.begin());
    
    
        thrust::device_vector<int> keys1(num_pairs);
        thrust::device_vector<int> keys2(num_pairs);
        thrust::device_vector<int> ids(num_pairs);
    
        SHOW_VECTOR(d_addrs,sz1);   
    
        //fill the vector with the keys and ids
        scatter_kernel<<<1,sz1>>>(RAW_CAST(d_keys),
                                  RAW_CAST(d_segs),
                                  RAW_CAST(d_addrs),
                                  RAW_CAST(keys1),
                                  RAW_CAST(keys2),
                                  RAW_CAST(ids),                              
                                  sz1);
    
        SHOW_VECTOR(keys1,num_pairs);
        SHOW_VECTOR(keys2,num_pairs);
        SHOW_VECTOR(ids,num_pairs);
    
    
        return 0;
    }