如何使用CUDA生成随机排列

时间:2012-09-29 15:28:30

标签: c++ algorithm cuda thrust

我可以使用哪些并行算法从给定集合生成随机排列? 特别是适合CUDA的论文提案或链接会有所帮助。

此序贯版本将是Fisher-Yates shuffle。

示例:

设S = {1,2,...,7}是源索引的集合。 目标是并行生成n个随机排列。 n个排列中的每一个都包含每个源索引一次, 例如{7,6,...,1}。

4 个答案:

答案 0 :(得分:13)

Fisher-Yates shuffle可以并行化。例如,4个并发工作者只需要3次迭代来混洗8个元素的向量。在第一次迭代时,它们交换0 -1,2 - 3,4 - 5,5 - 7;在第二次迭代0 - 2,1 - 3 - 4 - 5 - ,6 - 7;并且在最后一次迭代0 - 4,1 - 5,5 - 6,3 - 7。

ParallelFisherYates

这可以很容易地实现为CUDA __device__代码(受标准min/max reduction启发):

const int id  = threadIdx.x;
__shared__ int perm_shared[2 * BLOCK_SIZE];
perm_shared[2 * id]     = 2 * id;
perm_shared[2 * id + 1] = 2 * id + 1;
__syncthreads();

unsigned int shift = 1;
unsigned int pos = id * 2;  
while(shift <= BLOCK_SIZE)
{
    if (curand(&curand_state) & 1) swap(perm_shared, pos, pos + shift);
    shift = shift << 1;
    pos = (pos & ~shift) | ((pos & shift) >> 1);
    __syncthreads();
}

这里省略了curand初始化代码,方法swap(int *p, int i, int j)交换了值p[i]p[j]

请注意,上面的代码有以下假设:

  1. 排列长度为2 * BLOCK_SIZE,其中BLOCK_SIZE为2的幂。
  2. 2 * BLOCK_SIZE整数适合CUDA设备的__shared__内存
  3. BLOCK_SIZE是CUDA块的有效大小(通常介于32和512之间)
  4. 为了生成多个排列,我建议使用不同的CUDA块。如果目标是对7个元素进行排列(正如原始问题中提到的那样),那么我相信在单个线程中执行它会更快。

答案 1 :(得分:1)

如果s = s_L的长度,可以用推力实现这种非常粗略的方式:

http://thrust.github.com

首先,创建一个长度为s_L x n的向量val,重复s次。

创建一个向量val_keys将n个唯一键与val的每个元素重复s_L次,例如,

  val = {1,2,...,7,1,2,...,7,....,1,2,...7}
  val_keys = {0,0,0,0,0,0,0,1,1,1,1,1,1,2,2,2,...., n,n,n}

现在有趣的部分。创建一个长度为s_L x n的向量均匀分布的随机变量

  U  = {0.24, 0.1, .... , 0.83} 

然后你可以在val,val_keys上压缩迭代器并根据U:

对它们进行排序

http://codeyarns.com/2011/04/04/thrust-zip_iterator/

val,val_keys都将遍布整个地方,所以你必须使用thrust :: stable_sort_by_key()将它们重新组合在一起,以确保如果val [i]和val [j]都属于key [k ]和val [i]在随机排序之后的val [j]之前,然后在最终版本中val [i]仍然应该在val [j]之前。如果一切按计划进行,val_keys应该像以前一样,但是val应该反映出改组。

答案 2 :(得分:0)

答案 3 :(得分:0)

对于大型集合,在随机密钥向量上使用排序原语可能足以满足您的需求。首先,设置一些向量:

const int N = 65535;
thrust:device_vector<uint16_t> d_cards(N);
thrust:device_vector<uint16_t> d_keys(N);
thrust::sequence(d_cards.begin(), d_cards.end());

然后,每次你想要改组d_cards时都要调用这对:

thrust::tabulate(d_keys.begin(), d_keys.end(), PRNFunc(rand()*rand());
thrust::sort_by_key(d_keys.begin(), d_keys.end(), d_cards.begin());
// d_cards now freshly shuffled

随机密钥是从仿函数生成的,该仿函数使用种子(在主机代码中评估并在启动时复制到内核)和一个密钥编号(在创建线程时表格传入):

struct PRNFunc
{
  uint32_t seed;
  PRNFunc(uint32_t s) { seed = s; }
  __device__ __host__ uint32_t operator()(uint32_t kn) const
  {
    thrust::minstd_rand randEng(seed);
    randEng.discard(kn);
    return randEnd();
  }
};

如果我能弄清楚如何缓存内部推文:: sort_by_key的分配,我发现性能可以提高(大约30%)。

欢迎任何更正或建议。