我通过从AoS转换为SoA方法来重构推力代码,以利用内存合并。为此,我有两个由公共密钥缩减的向量,然后用于计算输出向量的值。原始代码使用单个仿函数完成此操作,我想模拟它。
本质:
Oᵢ=Rᵢ/Sᵢ,其中Rᵢ和Sᵢ是用相同的键减少的向量,Oᵢ是相应的输出向量。
下面的代码举例说明了我尝试做的事情:
typedef tuple<int,int> Tuple;
struct BinaryTupleOp : public thrust::binary_function<Tuple const &, Tuple const &, int>
{
__host__ __device__
int operator()(Tuple const & lhs, Tuple const & rhs) const {
// get<0> = vals, get<1> = other_vals
return (get<0>(lhs) + get<0>(rhs)) / (get<1>(lhs) + get<1>(rhs));
}
};
int main(int argc, char ** argv)
{
const int N = 7;
device_vector<int> keys(N);
keys[0] = 1; // represents sorted keys
keys[1] = 1;
keys[2] = 2;
keys[3] = 2;
keys[4] = 3;
keys[5] = 3;
keys[6] = 3;
device_vector<int> vals(N);
vals[0] = 6; // just some random numbers
vals[1] = 3;
vals[2] = 9;
vals[3] = 4;
vals[4] = 6;
vals[5] = 1;
vals[6] = 5;
device_vector<int> other_vals(N);
other_vals[0] = 4; // more randomness
other_vals[1] = 1;
other_vals[2] = 3;
other_vals[3] = 6;
other_vals[4] = 2;
other_vals[5] = 5;
other_vals[6] = 7;
device_vector<int> new_keys(N);
device_vector<int> output(N);
typedef device_vector<int>::iterator Iterator;
thrust::pair<Iterator, Iterator> new_end;
thrust::equal_to<int> binary_pred;
new_end = thrust::reduce_by_key(keys.begin(), keys.end(),
make_zip_iterator(make_tuple(vals.begin(), other_vals.begin())),
new_keys.begin(),
output.begin(),
binary_pred,
BinaryTupleOp() );
Iterator i = new_keys.begin();
Iterator j = output.begin();
for (;
i != new_end.first;
i++, j++ ) {
std::cout << "key " << *i << " sum " << *j << endl;
}
return 0;
}
不幸的是,这会产生error: no operator "=" matches these operands
,error: no suitable conversion function from "InputValueType" to "TemporaryType" exists
和error: no suitable conversion function from "const thrust::tuple<int, int, thrust::null_type, thrust::null_type, thrust::null_type, thrust::null_type, thrust::null_type, thrust::null_type, thrust::null_type, thrust::null_type>" to "int" exists
等错误。我在仿函数中使用了参数类型变体,因为我认为它是问题的最终来源,但无济于事。
作为一种解决方法,我可能会分别打破两个减少,然后使用变换来创建输出向量。 (这可能建议将各种transform_reduce()
个电话联系在一起,但似乎我想要反向,例如reduce_transform()
,它不存在,AFAIK。)
与此同时,我做错了什么?
答案 0 :(得分:3)
与此同时,我做错了什么?
thrust::reduce
(或thrust::reduce_by_key
)将执行并行缩减。这种并行减少需要可以成对应用的减少算子。举一个非常简单的例子,假设我们要减少3个元素(E1
,E2
和E3
),我们将进行二元操作(bOp
)用于定义还原操作。推力可能会做这样的事情:
E1 E2 E3
\ /
bOp
\ /
bOp
|
result
也就是说,二元操作将用于组合或&#34;减少&#34;将元素E1
和E2
合并为一个临时的部分结果,并将此结果反馈到与元素E3
结合的二元运算中,以生成最终的result
。
这意味着二进制op的输出(因此值输出迭代器的输出类型)必须与其输入类型匹配(因此输入迭代器的值的输入类型)。
但是你的二进制op不符合这个要求,你为值输入和值输出传递的迭代器类型也没有:
new_end = thrust::reduce_by_key(keys.begin(), keys.end(),
make_zip_iterator(make_tuple(vals.begin(), other_vals.begin())),
/* dereferencing the above iterator produces an <int, int> tuple */
new_keys.begin(),
output.begin(),
/* dereferencing the above iterator produces an int */
binary_pred,
BinaryTupleOp() );
我认为二进制op的上述一般要求(即值输入和输出迭代器类型)在thrust docs for this function表示为:
InputIterator2的value_type可转换为OutputIterator2的value_type。
我可以想出两种方法来解决你在这里指出的问题:
Oᵢ=Rᵢ/Sᵢ,其中Rᵢ和Sᵢ是用相同的键减少的向量,Oᵢ是相应的输出向量。
我认为你已经提到的第一个:
仅对压缩在一起的两个值序列执行reduce_by_key
。这可以在一次reduce_by_key
电话中完成。然后将压缩的结果序列传递给thrust::transform
调用以执行元素分割。
如果您迫切希望在一次推力调用中完成所有事情,那么聪明的输出迭代器工作由@m.s完成。 here可能是一种可能的方法,用于对值输出执行变换操作。 (编辑:经过一些研究,我不确定这种方法可以用于减少)
如果你不喜欢上述任何一种建议,那么可以通过一次额外的存储(可以说是一些存储)来实现对reduce_by_key
的单次呼叫的减少。浪费的操作)。基本思想是组织一个3元组而不是2元组。在每个缩减步骤中,我们将组合(求和)lhs
和rhs
元组的相应(第一和第二)组件,将这些结果存储在输出元组的第一个和第二个位置。另外,我们将计算输出元组的第三位置的值作为输出元组的第一和第二位置的划分的结果。您正在此处执行int
操作,包括除法,因此我选择稍微修改您的输入数据以便于结果验证。这是一个有效的例子,基于你的代码:
$ cat t1143.cu
#include <thrust/device_vector.h>
#include <thrust/reduce.h>
#include <thrust/iterator/zip_iterator.h>
#include <thrust/iterator/constant_iterator.h>
#include <iostream>
using namespace std;
using namespace thrust;
typedef tuple<int,int,int> Tuple;
struct BinaryTupleOp : public thrust::binary_function<const Tuple &, const Tuple &, Tuple>
{
__host__ __device__
Tuple operator()(const Tuple & lhs, const Tuple & rhs) const {
Tuple temp;
get<0>(temp) = get<0>(lhs)+get<0>(rhs);
get<1>(temp) = get<1>(lhs)+get<1>(rhs);
get<2>(temp) = get<0>(temp)/get<1>(temp);
return temp;
}
};
int main(int argc, char ** argv)
{
const int N = 7;
device_vector<int> keys(N);
keys[0] = 1; // represents sorted keys
keys[1] = 1;
keys[2] = 2;
keys[3] = 2;
keys[4] = 3;
keys[5] = 3;
keys[6] = 3;
device_vector<int> vals(N);
vals[0] = 6; // just some random numbers
vals[1] = 3;
vals[2] = 8;
vals[3] = 4;
vals[4] = 5;
vals[5] = 5;
vals[6] = 5;
device_vector<int> other_vals(N);
other_vals[0] = 1; // more randomness
other_vals[1] = 2;
other_vals[2] = 1;
other_vals[3] = 2;
other_vals[4] = 1;
other_vals[5] = 1;
other_vals[6] = 1;
device_vector<int> new_keys(N);
device_vector<int> output(N);
device_vector<int> v1(N);
device_vector<int> v2(N);
thrust::equal_to<int> binary_pred;
int rsize = thrust::get<0>(thrust::reduce_by_key(keys.begin(), keys.end(),
make_zip_iterator(make_tuple(vals.begin(), other_vals.begin(), thrust::constant_iterator<int>(0))),
new_keys.begin(),
make_zip_iterator(make_tuple(v1.begin(), v2.begin(), output.begin())),
binary_pred,
BinaryTupleOp() )) - new_keys.begin();
for (int i = 0; i < rsize; i++){
int key = new_keys[i];
int val = output[i];
std::cout << "key " << key << " sum " << val << endl;
}
return 0;
}
$ nvcc -o t1143 t1143.cu
$ ./t1143
key 1 sum 3
key 2 sum 4
key 3 sum 5
$
其他人可能对如何制作您想要的结果有更好的想法。