你将如何在CUDA中实现这个功能? (有序整数向量中的偏移量)

时间:2011-11-15 10:18:05

标签: algorithm cuda thrust

我在设备上有一个排序的整数数组,例如:

[0,0,0,1,1,2,2]

我希望对另一个数组中的每个元素进行偏移:

[0,3,5]

(因为第一个0位于第0位,第一个位于第3位,依此类推) 我知道预先会有多少不同的元素。你会如何在CUDA中有效地实现这一点?我不是要求代码,而是要求您实现的算法的高级描述来计算此转换。我已经看过推力名称空间中的各种函数,但是没想到推力函数的任何组合来实现这一点。此外,这种转变是否具有广泛接受的名称?

4 个答案:

答案 0 :(得分:4)

虽然我从未使用推力库,但这种可能的方法(简单但可能有效)如何:

int input[N];  // your sorted array
int offset[N]; // the offset of the first values of each elements. Initialized with -1

// each thread will check an index position
if (input[id] > input[id-1]) // bingo! here begins a new value
{
    int oid = input[id];  // use the integer value as index
    offset[oid] = id;     // mark the offset with the beginning of the new value
}

在您的示例中,输出将为:

[0,3,5]

但是如果输入数组是:

[0,0,0,2,2,4,4]

然后输出将是:

[0,-1, 3, -1, 5]

现在,如果推力可以为你做,remove_if(offset [i] == -1)并压缩数组。

这种方法会为偏移数组浪费大量内存,但是由于你不知道要找到多少偏移量,最坏的情况是使用与输入数组一样多的内存。

另一方面,与全局内存负载相比,每个线程的少数指令将通过内存带宽限制此实现。这种情况有一些优化,因为每个线程处理一些值。

我的2美分!

答案 1 :(得分:4)

您可以使用thrust::unique_by_key_copy thrust::counting_iterator使用keys在Thrust中解决此问题。我们的想法是将整数数组视为unique_by_key_copy的{​​{1}}参数,并使用一系列升序整数(即counting_iterator)作为valuesunique_by_key_copy会将values数组压缩为每个唯一key的索引:

#include <thrust/device_vector.h>
#include <thrust/iterator/counting_iterator.h>
#include <thrust/iterator/discard_iterator.h>
#include <thrust/unique.h>
#include <thrust/copy.h>
#include <iterator>
#include <iostream>

int main()
{
  thrust::device_vector<int> keys(7);
  keys[0] = 0; keys[1] = 0; keys[2] = 0;
  keys[3] = 1; keys[4] = 1; keys[5] = 2; keys[6] = 2;

  std::cout << "keys before unique_by_key_copy: [ ";
  thrust::copy(keys.begin(), keys.end(), std::ostream_iterator<int>(std::cout," "));
  std::cout << "]" << std::endl;

  thrust::device_vector<int> offsets(3);

  thrust::unique_by_key_copy(keys.begin(), keys.end(),          // keys
                             thrust::make_counting_iterator(0), // [0, 1, 2, 3, ...] are the values
                             thrust::make_discard_iterator(),   // discard the compacted keys
                             offsets.begin());                  // the offsets are the values

  std::cout << "offsets after unique_by_key_copy: [ ";
  thrust::copy(offsets.begin(), offsets.end(), std::ostream_iterator<int>(std::cout," "));
  std::cout << "]" << std::endl;

  return 0;
}

这是输出:

$ nvcc test.cu -run
keys before unique_by_key_copy: [ 0 0 0 1 1 2 2 ]
offsets after unique_by_key_copy: [ 0 3 5 ]

答案 2 :(得分:1)

扫描是您正在寻找的算法。如果您没有实现,Thrust库将是一个很好的资源。 (寻找推力::扫描)

扫描(或“并行前缀和”)采用输入数组并生成输出,其中每个元素是该点的输入之和:[1 5 3 7] =&gt; [1 6 9 16]

如果扫描谓词(0或1,具体取决于评估条件),谓词检查给定元素是否与前一个元素相同,则计算相关元素的输出索引。你的示例数组

[0 0 0 1 1 2 2] [0 0 0 1 0 1 0]&lt; =谓词 [0 0 0 1 1 2 2]&lt; =扫描的谓词

现在,您可以使用扫描的谓词作为索引来编写输出。

答案 3 :(得分:0)

好问题,答案取决于你之后需要做什么。让我解释一下。

只要在CPU上的O(n)(其中n是输入长度)中解决了这个问题,就会遭受内存分配和复制(主机 - >设备(输入)和设备 - &gt;主机) (结果))缺点。这将导致简单CPU解决方案的性能下降。

即使您的阵列已经存在于设备存储器中,每个计算块都需要将其读取到本地或寄存器(至少是访问设备存储器),并且它不能比在CPU上快得多。

一般情况下,CUDA可以在以下情况下加快性能:

  1. 与输入数据长度相比,计算的渐近复杂度很高。例如,输入数据长度为n,复杂度为O(n ^ 2)或O(n ^ 3)。

  2. 有办法将任务拆分为独立或弱依赖子任务。

  3. 所以,如果我是你,如果可能的话,我不会尝试在CUDA上进行这样的计算。如果它必须是一些独立的函数或输出格式转换为我在CPU中做的其他功能。

    如果它是一些更复杂的算法的一部分,答案就更复杂了。如果我在你的位置,我会尝试以某种方式改变[0,3,5]格式,因为它增加了利用CUDA计算能力的限制。您无法在独立块上有效地拆分任务。例如,如果我在一个计算线程中处理10个整数,而在另一个计算线程中处理10个整数。第二个不知道在第一个未完成之前将输出放在何处。可能是我将在子阵列上拆分数组并分别存储每个子阵列的答案。这在很大程度上取决于您正在进行的计算。