将数据从较小的矢量复制到较大的矢量

时间:2015-12-05 21:48:58

标签: c++ cuda thrust

我正在使用Thrust开发GPU项目。我不打算解释我在做什么,而是提供一个简单,略微通用的场景,更容易解释,并可能在将来帮助其他人。

假设我有一个向量,我想修改向量的每个第三个元素。

我能想到的两个解决方案是:

1)使用像变换这样的推力调用来修改每个第三个元素,可能带有谓词或其他东西。

2)将每个第三个元素复制到一个较小的向量中,对其进行调用转换,将这些元素复制回原始向量的原始点。

使用Thrust可以使用其中任何一个吗?

还有其他办法或更好的办法解决这个问题吗?

感谢所有建议!

1 个答案:

答案 0 :(得分:0)

  

使用Thrust可以使用其中任何一种吗?

是的,两者都有可能。

  

还有其他办法或更好的办法解决这个问题吗?

在某种程度上,最佳方法可能会有所不同,具体取决于您的应用程序中此数据可能发生的其他情况。但是在你所概述的范围内,我认为推力strided range可能是一个不错的选择,可能是最好的选择。

当然,您的第一种方法是可行的,使用适当定义的函数来调整行为。 (例如,使用constant_iterator压缩数据以提供数据"索引",并使用仿函数条件对相应的"索引")进行数据转换。但是它会遇到这样的缺点:我们需要启动3倍的线程(因为3个线程中只有1个正在进行任何实际的向量修改)。跨步范围方法对此进行了改进,因为每个线程都将执行修改所选向量元素的工作,并且没有浪费"线程。

这种方法仍有一定程度的效率低下"因为GPU数据加载特性,因为我们访问的数据是3倍(使用functor /谓词方法,或者是跨步范围方法)。您的第二种方法(将每个第3个元素复制到较小的向量)可以减轻这种低效率,但是您需要支付数据复制操作的成本,这将抵消单个transform操作的上下文带来的任何好处。但是,如果您希望在此缩小的向量上执行许多其他步骤,则可以通过多个剩余操作的顺序恢复将数据复制到较小向量的开销/成本,这些操作不支付&# 34;低效率"访问3倍的数据。

然而,跨步范围方法仍然可用于将较大向量中的元素复制到较小向量,或者直接在较大向量上启动transform操作,但仅修改特定元素。

这里有一个实例,基本上是对跨步范围示例的一个微不足道的修改,它演示了两种可能的方法 - 第一种是复制然后转换,第二种是就地转换:

$ cat t996.cu
#include <thrust/iterator/counting_iterator.h>
#include <thrust/iterator/transform_iterator.h>
#include <thrust/iterator/permutation_iterator.h>
#include <thrust/functional.h>
#include <thrust/fill.h>
#include <thrust/device_vector.h>
#include <thrust/copy.h>
#include <thrust/transform.h>
#include <iostream>

#define STRIDE 3

// this example illustrates how to make strided access to a range of values
// examples:
//   strided_range([0, 1, 2, 3, 4, 5, 6], 1) -> [0, 1, 2, 3, 4, 5, 6]
//   strided_range([0, 1, 2, 3, 4, 5, 6], 2) -> [0, 2, 4, 6]
//   strided_range([0, 1, 2, 3, 4, 5, 6], 3) -> [0, 3, 6]
//   ...

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;
};

int main(void)
{
    thrust::device_vector<int> data(8);
    data[0] = 10;
    data[1] = 20;
    data[2] = 30;
    data[3] = 40;
    data[4] = 50;
    data[5] = 60;
    data[6] = 70;
    data[7] = 80;

    // print the initial data
    std::cout << "initial data: " << std::endl;
    thrust::copy(data.begin(), data.end(), std::ostream_iterator<int>(std::cout, " "));  std::cout << std::endl;

    typedef thrust::device_vector<int>::iterator Iterator;

    // create strided_range with indices [0,3,6]
    strided_range<Iterator> strided(data.begin(), data.end(), STRIDE);
    // method 1: copy data from larger vector to smaller, then transform it:
    thrust::device_vector<int> result1(data.size()/STRIDE+1);
    thrust::copy(strided.begin(), strided.end(), result1.begin());
    thrust::transform(result1.begin(), result1.end(), result1.begin(), thrust::negate<int>());
    std::cout << "method 1 result: " << std::endl;
    thrust::copy(result1.begin(), result1.end(), std::ostream_iterator<int>(std::cout, " "));
    std::cout << std::endl;

    // method 2: transform data "in-place":
    std::cout << "method 2 result: " << std::endl;
    thrust::transform(strided.begin(), strided.end(), strided.begin(), thrust::negate<int>());
    thrust::copy(data.begin(), data.end(), std::ostream_iterator<int>(std::cout, " "));  std::cout << std::endl;

    return 0;
}
$ nvcc -o t996 t996.cu
$ ./t996
initial data:
10 20 30 40 50 60 70 80
method 1 result:
-10 -40 -70
method 2 result:
-10 20 30 -40 50 60 -70 80
$