使用CUDA Thrust确定每个矩阵列中的最小元素及其位置

时间:2013-07-17 11:47:46

标签: c++ cuda thrust

我有一个相当简单的问题,但我无法找到一个优雅的解决方案。

我有一个Thrust代码,它生成包含值的相同大小的c个向量。假设这些c向量中的每一个都有一个索引。我想为每个向量位置获取值为最低值的c向量的索引:

示例:

C0 =     (0,10,20,3,40)
C1 =     (1,2 ,3 ,5,10)

我会得到一个包含C向量索引的向量,该向量具有最低值:

result = (0,1 ,1 ,0,1)

我已经考虑过使用push zip迭代器来实现它,但是遇到了各种问题:我可以压缩所有c向量并实现一个任意转换,它接受一个元组并返回其最低值的索引,但是:

  1. 如何迭代元组的内容?
  2. 据我了解,元组最多只能存储10个元素,并且可以存储10 c个向量。
  3. 我已经考虑过这样做:不是将c分开的向量,而是将它们全部附加到单个向量C中,然后生成引用位置的键并按键执行稳定排序这将从同一位置重新组合矢量条目。在给出的示例中:

    C =      (0,10,20,3,40,1,2,3,5,10)
    keys =   (0,1 ,2 ,3,4 ,0,1,2,3,4 )
    after stable sort by key:
    output = (0,1,10,2,20,3,3,5,40,10)
    keys =   (0,0,1 ,1,2 ,2,3,3,4 ,4 )
    

    然后使用向量中的位置生成关键字,使用c向量的索引对输出进行压缩,然后使用自定义函数执行按键缩减,每个约简都会输出具有最低值的索引。在示例中:

    input =  (0,1,10,2,20,3,3,5,40,10)
    indexes= (0,1,0 ,1,0 ,1,0,1,0 ,1)
    keys =   (0,0,1 ,1,2 ,2,3,3,4 ,4)
    after reduce by keys on zipped input and indexes:
    output = (0,1,1,0,1)
    

    但是,如何通过键操作来编写这样的仿函数?

3 个答案:

答案 0 :(得分:4)

一个可能的想法,建立在矢量化排序的想法here

  1. 假设我有这样的矢量:

    values:    C =      ( 0,10,20, 3,40, 1, 2, 3, 5,10)
    keys:      K =      ( 0, 1, 2, 3, 4, 0, 1, 2, 3, 4)
    segments:  S =      ( 0, 0, 0, 0, 0, 1, 1, 1, 1, 1)
    
  2. 将K和S压缩在一起以创建KS

  3. stable_sort_by_key使用C作为键,KS作为值:

    stable_sort_by_key(C.begin(), C.end(), KS_begin);
    
  4. 将重新排序的C和K向量压缩在一起,以创建CK

  5. stable_sort_by_key使用重新排序的S作为键,CK作为值:

    stable_sort_by_key(S.begin(), S.end(), CK_begin);
    
  6. 使用permutation iteratorstrided range iterator访问新重新排序的K向量的每个第N个元素(0,N,2N,...),以检索向量每个段中min元素的索引,其中N是段的长度。

  7. 我实际上没有实现这个,现在它只是一个想法。也许由于某些原因我还没有观察到它会起作用。

    segmentsS)和keysK)实际上是行和列索引。

    你的问题对我来说似乎很奇怪,因为你的标题提到“找到最大值的索引”但你的大部分问题似乎都是指“最低价值”。无论如何,通过更改我的算法的第6步,您可以找到任一值。

答案 1 :(得分:4)

因为矢量的长度必须相同。最好将它们连接在一起并将它们视为矩阵C.

然后你的问题就是找到行主矩阵中每列的min元素的索引。它可以解决如下。

  1. 将row-major更改为col-major;
  2. 查找每列的索引。
  3. 在步骤1中,您建议使用stable_sort_by_key重新排列元素顺序,这不是一种有效的方法。由于可以在给定矩阵的#row和#col的情况下直接计算重排。在推力中,可以使用置换迭代器来完成:

    thrust::make_permutation_iterator(
        c.begin(),
        thrust::make_transform_iterator(
            thrust::make_counting_iterator((int) 0),
            (_1 % row) * col + _1 / row)
    )
    

    在第2步中,reduce_by_key可以完全按照您的意愿行事。在你的情况下,简化二元op函子是很容易的,因为已经定义了元组(压缩矢量的元素)的比较来比较元组的第一个元素,并且它被推力支持

    thrust::minimum< thrust::tuple<float, int> >()
    

    整个程序如下所示。因为我在花式迭代器中使用占位符,所以需要Thrust 1.6.0+。

    #include <iterator>
    #include <algorithm>
    
    #include <thrust/device_vector.h>
    #include <thrust/iterator/counting_iterator.h>
    #include <thrust/iterator/transform_iterator.h>
    #include <thrust/iterator/permutation_iterator.h>
    #include <thrust/iterator/zip_iterator.h>
    #include <thrust/iterator/discard_iterator.h>
    #include <thrust/reduce.h>
    #include <thrust/functional.h>
    
    using namespace thrust::placeholders;
    
    int main()
    {
    
        const int row = 2;
        const int col = 5;
        float initc[] =
                { 0, 10, 20, 3, 40, 1, 2, 3, 5, 10 };
        thrust::device_vector<float> c(initc, initc + row * col);
    
        thrust::device_vector<float> minval(col);
        thrust::device_vector<int> minidx(col);
    
        thrust::reduce_by_key(
                thrust::make_transform_iterator(
                        thrust::make_counting_iterator((int) 0),
                        _1 / row),
                thrust::make_transform_iterator(
                        thrust::make_counting_iterator((int) 0),
                        _1 / row) + row * col,
                thrust::make_zip_iterator(
                        thrust::make_tuple(
                                thrust::make_permutation_iterator(
                                        c.begin(),
                                        thrust::make_transform_iterator(
                                                thrust::make_counting_iterator((int) 0), (_1 % row) * col + _1 / row)),
                                thrust::make_transform_iterator(
                                        thrust::make_counting_iterator((int) 0), _1 % row))),
                thrust::make_discard_iterator(),
                thrust::make_zip_iterator(
                        thrust::make_tuple(
                                minval.begin(),
                                minidx.begin())),
                thrust::equal_to<int>(),
                thrust::minimum<thrust::tuple<float, int> >()
        );
    
        std::copy(minidx.begin(), minidx.end(), std::ostream_iterator<int>(std::cout, " "));
        std::cout << std::endl;
        return 0;
    }
    

    剩下的两个问题可能会影响效果。

    1. 必须输出最小值,这不是必需的;
    2. reduce_by_key是针对具有变体长度的细分而设计的,它可能不是减少具有相同长度的细分的最快算法。
    3. 编写自己的内核可能是获得最高性能的最佳解决方案。

答案 2 :(得分:1)

我有好奇心去测试以前哪种方法更快。所以,我在下面的代码中实现了Robert Crovella的想法,为了完整起见,还报告了Eric的方法。

#include <iterator>
#include <algorithm>

#include <thrust/random.h>
#include <thrust/device_vector.h>
#include <thrust/iterator/counting_iterator.h>
#include <thrust/iterator/transform_iterator.h>
#include <thrust/iterator/permutation_iterator.h>
#include <thrust/iterator/zip_iterator.h>
#include <thrust/iterator/discard_iterator.h>
#include <thrust/reduce.h>
#include <thrust/functional.h>
#include <thrust/sort.h>

#include "TimingGPU.cuh"

using namespace thrust::placeholders;

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


/**************************************************************/
/* CONVERT LINEAR INDEX TO ROW INDEX - NEEDED FOR APPROACH #1 */
/**************************************************************/
template< typename T >
struct mod_functor {
    __host__ __device__ T operator()(T a, T b) { return a % b; }
};

/********/
/* MAIN */
/********/
int main()
{
    /***********************/
    /* SETTING THE PROBLEM */
    /***********************/
    const int Nrows = 200;
    const int Ncols = 200;

    // --- Random uniform integer distribution between 10 and 99
    thrust::default_random_engine rng;
    thrust::uniform_int_distribution<int> dist(10, 99);

    // --- Matrix allocation and initialization
    thrust::device_vector<float> d_matrix(Nrows * Ncols);
    for (size_t i = 0; i < d_matrix.size(); i++) d_matrix[i] = (float)dist(rng);

    TimingGPU timerGPU;

    /******************/
    /* APPROACH NR. 1 */
    /******************/
    timerGPU.StartCounter();

    thrust::device_vector<float>    d_min_values(Ncols);
    thrust::device_vector<int>      d_min_indices_1(Ncols);

    thrust::reduce_by_key(
            thrust::make_transform_iterator(
                    thrust::make_counting_iterator((int) 0),
                    _1 / Nrows),
            thrust::make_transform_iterator(
                    thrust::make_counting_iterator((int) 0),
                    _1 / Nrows) + Nrows * Ncols,
            thrust::make_zip_iterator(
                    thrust::make_tuple(
                            thrust::make_permutation_iterator(
                                    d_matrix.begin(),
                                    thrust::make_transform_iterator(
                                            thrust::make_counting_iterator((int) 0), (_1 % Nrows) * Ncols + _1 / Nrows)),
                            thrust::make_transform_iterator(
                                    thrust::make_counting_iterator((int) 0), _1 % Nrows))),
            thrust::make_discard_iterator(),
            thrust::make_zip_iterator(
                    thrust::make_tuple(
                            d_min_values.begin(),
                            d_min_indices_1.begin())),
            thrust::equal_to<int>(),
            thrust::minimum<thrust::tuple<float, int> >()
    );

    printf("Timing for approach #1 = %f\n", timerGPU.GetCounter());

    /******************/
    /* APPROACH NR. 2 */
    /******************/
    timerGPU.StartCounter();

    // --- Computing row indices vector
    thrust::device_vector<int> d_row_indices(Nrows * Ncols);
    thrust::transform(thrust::make_counting_iterator(0), thrust::make_counting_iterator(Nrows * Ncols), thrust::make_constant_iterator(Ncols), d_row_indices.begin(), thrust::divides<int>() );

    // --- Computing column indices vector
    thrust::device_vector<int> d_column_indices(Nrows * Ncols);
    thrust::transform(thrust::make_counting_iterator(0), thrust::make_counting_iterator(Nrows * Ncols), thrust::make_constant_iterator(Ncols), d_column_indices.begin(), mod_functor<int>());

    // --- int and float iterators
    typedef thrust::device_vector<int>::iterator        IntIterator;
    typedef thrust::device_vector<float>::iterator      FloatIterator;

    // --- Relevant tuples of int and float iterators
    typedef thrust::tuple<IntIterator, IntIterator>     IteratorTuple1;
    typedef thrust::tuple<FloatIterator, IntIterator>   IteratorTuple2;

    // --- zip_iterator of the relevant tuples
    typedef thrust::zip_iterator<IteratorTuple1>        ZipIterator1;
    typedef thrust::zip_iterator<IteratorTuple2>        ZipIterator2;

    // --- zip_iterator creation
    ZipIterator1 iter1(thrust::make_tuple(d_column_indices.begin(), d_row_indices.begin()));

    thrust::stable_sort_by_key(d_matrix.begin(), d_matrix.end(), iter1);

    ZipIterator2 iter2(thrust::make_tuple(d_matrix.begin(), d_row_indices.begin()));

    thrust::stable_sort_by_key(d_column_indices.begin(), d_column_indices.end(), iter2);

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

    // --- Strided access to the sorted array
    strided_range<Iterator> d_min_indices_2(d_row_indices.begin(), d_row_indices.end(), Nrows);

    printf("Timing for approach #2 = %f\n", timerGPU.GetCounter());

    printf("\n\n");
    std::copy(d_min_indices_2.begin(), d_min_indices_2.end(), std::ostream_iterator<int>(std::cout, " "));
    std::cout << std::endl;

    return 0;
}

测试2000x2000大小矩阵的两种方法,这是Kepler K20c卡的结果:

Eric's             :  8.4s
Robert Crovella's  : 33.4s