假设我有
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)
答案 0 :(得分:4)
事实证明这比我最初的想法更为复杂。我的解释是,你基本上想要计算每次采取两个N个事物的所有组合,而不是重复,每个段(每个段的N是不同的)。我将提供一个我认为涵盖您可能想要考虑的大多数概念的答案。当然,这只是一种可能的解决方法。
这可能无法达到您想要的解决方案(尽管我认为它非常接近)。我的目的不是为您提供完成的代码,而是为了演示可能的算法方法。
以下是该算法的演练:
按段对键进行排序。如果您可以保证类似的键(在一个段内)被组合在一起,则不需要此步骤。 (您提供的数据不需要此步骤。)我们可以使用背靠背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,
将每个段的数据集简化为唯一键(删除重复的键。)我们可以在键和段上与thrust::unique_copy
一起执行此操作,并使用适当的函数来定义键的相等性只在一个细分中。输出:
d_ukeys:
1,2,3,1,1,2,1,
d_usegs:
0,0,0,1,2,2,3,
现在计算删除重复键时每个段的长度。我们可以使用thrust::reduce_by_key
和constant_iterator
执行此操作。输出:
d_seg_lens:
3,1,2,1,
现在我们知道每个段长度,我们想要删除段长度为1的任何段(加上它的关联键)。这些段没有任何可能的键对。为了实现这一点,我们还需要计算每个段的起始索引并创建一个模板来标识长度为1的段,然后我们可以使用remove_if
和scatter_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,
此时我们的目标是创建一个适当长度的向量,以便它可以为每次采用两个N个事物的每个组合保留一个条目,其中N是每个段的长度。因此,对于上面的第一部分,它有3个项目,因此一次2个采取的3件事的组合需要存储3个组合总计。同样,长度2以上的第二段在2个键之间仅具有一个唯一组合,因此对于该段仅一个组合需要存储。我们可以使用标准组合公式计算此存储长度,该公式减少为一次采取2个N项的组合:
C = (N)*(N-1)/2
我们将使用此公式,与我们的段长度一起传递给thrust::transform_reduce
,以计算所需的总存储长度。 (对于此示例,总共4个组合)。
一旦我们确定了总长度,并分配了该长度的必要向量,我们需要生成属于每个段的实际组合。这又是一个多步骤序列,首先生成标记以标记(描绘)这些向量中的每个“段”。使用flag数组,我们可以使用exclusive_scan_by_key
生成一个序数序列,用于标识每个段中每个位置所属的组合。输出:
d_flags:
1,0,0,1,
example ordinal sequence:
0,1,2,0
对于每个段的序数序列,我们现在需要为每个段生成实际的唯一组合。这个步骤需要考虑尝试并提出一种算法来在固定的时间内完成这个(即没有迭代)。我想出的算法是将每个序数序列映射到一个矩阵,因此对于一个有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中讨论的方法相当,我最近偶然发现,尽管它们乍一看似乎有所不同。)
最后一个转换步骤是将逻辑键和段转换为“实际”键和段。输出:
d_lkey1:
1,2,2,1,
d_lkey2:
0,0,1,0,
这是一个实现上述功能的完整代码。它可能无法完全按照您的要求进行操作,但我认为它与您所描述的非常接近,如果您希望以推力执行此操作,希望这对于创意有用:
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)。在第一步中,它只计算输出数组中的元素数,第二步是填充数组。 基本上算法运行两次:第一次“模拟”写入,第二次实际写入输出。 这是我在输出大小取决于输入数据时使用的常见模式。
以下是步骤:
以下是我算法的输出:
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;
}