在cuda内核中动态扩展数组

时间:2019-01-17 19:07:53

标签: algorithm c++11 graph cuda

我试图在GPU上运行Brandes算法(基本上是带有一些额外操作和数据结构的bfs),并且我为每个线程分配一个顶点来运行brandes。我面临的问题是在我的代码中

  

我需要存储在bfs期间访问的每个顶点的父代

。在CPU实现中,只要创建一个向量映射并在我找到一个新的父对象(从技术上讲是一个动态扩展的数组)时调用push_back,就很容易实现。我不知道如何在CUDA中做到这一点。

以下是我需要的功能的示例代码:

    vector<int> distance;               //Initialized to 0
    vector<int> paths;                  //Initialized to 0
    vector<bool> visited;               //Initialized to false
    map <int, vector<int> > parents;    //Parent vector of each key is empty
    queue<int> q;


    // Running bfs from vertex
    q.push(vertex);                     
    while(!q.empty())
    {
        int source = q.front();
        q.pop();

        for(auto neighbour : adjacency_list[source])
        {
            if(!visited[neighbour])
            {
                visited[neighbour] = true;
                q.push(neighbour);
                distance[neighbour] = distance[source] + 1;
            }
            if(distance[neighbour] == distance[source] + 1)
            {
                paths[neighbour] += paths[source];
                parents[neighbour].push_back(source);
            }
        }
    }

    {
        // Use data accumulated above for calculations
        ....
    }

这是我在设备代码中实现时遇到的问题(功能)

  

父母[邻居] .push_back(源);

我的印象:

  1. 我可以为每个顶点过度分配(最大图的度数)父级列表,但这将使我浪费大量未使用的内存

  2. 将父级关系存储为大小为2 * Edges的数组中的边,但是我需要将顶点的所有父级一起存储(连续存储或存储在同一容器中),这在该实现中是不可能的

  3. 我知道gpu堆内存,但是想不出一种方法来利用它供我使用

  4. 最坏的情况:我首先运行bfs来找到否。每个顶点的父代,然后为每个顶点分配适当的内存,然后再次运行brandes。

1 个答案:

答案 0 :(得分:0)

  1. 我认为您的印象1可以用here(每线程堆栈,已预先分配)大致实现。它有您提到的与过度分配有关的问题。在较新的GPU中,内存通常为几GB(或更多),因此,如果总内存不是问题,那么对过度分配的关注可能不是很严重。

  2. 我认为您的印象2可以用here(设备范围的线程安全矢量push_back)大致实现。它具有您提到的问题,与结果向量中结果的排序不足有关。收集操作完成后,可以通过排序操作解决这些问题。

(4。听起来您可能已经对如何做“最坏情况”的印象4有了想法。)

  1. 剩下印象3。我们可以使用印象1和印象2的组合,即创建每个线程的向量push_back,但可以通过内核malloc使用按需分配,或者new。这样的内核内存分配非常缓慢,并且并非没有其自身的问题(例如,您可能必须保留额外的堆空间,内核分配的堆内存无法参与向主机的传输,小的分配可能会导致内存效率低下)用法),但是如果没有更多有关问题范围的信息,实际上是没有办法告诉您哪种方法最好。如果在遍历图形时跟踪父节点是相对少见的操作,则动态分配方法可能不是问题。

这是一个如何创建简单向量(每个线程)的示例:

$ cat t376.cu
#include <iostream>
#include <cstdio>

#include <assert.h>
template <typename T>
class cu_vec{  // simple implementation of per-thread "vector"
  const size_t alloc_block_size = 4096; // tuning parameter
  T *my_ptr;
  size_t n_items;
  size_t alloc_blocks;
  public:
    __host__ __device__
    cu_vec(){
      assert(sizeof(T) <= alloc_block_size);
      n_items = 0;
      my_ptr = (T *)new char[alloc_block_size];
      assert(my_ptr != NULL);
      alloc_blocks = 1;}

    __host__ __device__
    cu_vec(size_t sz){
      assert(sizeof(T) <= alloc_block_size);
      n_items = sz;
      alloc_blocks = (n_items*sizeof(T)+alloc_block_size-1)/alloc_block_size;
      my_ptr = (T *)new char[alloc_blocks*alloc_block_size];
      assert(my_ptr != NULL);
      memset(my_ptr, 0, alloc_blocks*alloc_block_size);}

    __host__ __device__
    ~cu_vec(){
      if (my_ptr != NULL) delete[] my_ptr;
      }

    __host__ __device__
    void push_back(T const &item){ // first test if we can just store new item
      if ((n_items+1)*sizeof(T) > alloc_blocks*alloc_block_size){
        T *temp = (T *)new char[(alloc_blocks+1)*alloc_block_size];
        assert(temp != NULL);
        memcpy(temp, my_ptr, alloc_blocks*alloc_block_size);
        delete[] my_ptr;
        my_ptr = temp;
        alloc_blocks++;}
      my_ptr[n_items] = item;
      n_items++;}

    __host__ __device__
    size_t size(){
      return n_items;}

    __host__ __device__
    void clear(){
      n_items = 0;}

    __host__ __device__
    T& operator[](size_t idx){
      assert(idx < n_items);
      return my_ptr[idx];}

    __host__ __device__
    T& pop_back(){
      if (n_items > 0){
        n_items--;}
      return my_ptr[n_items];}

    __host__ __device__
    T* data(){
      return my_ptr;}

    __host__ __device__
    size_t storage_ratio(){
      return alloc_block_size/sizeof(T);}
};

struct ss
{
   unsigned x;
   float y;
};

__global__ void test(){

  cu_vec<ss> my_vec;
  ss temp = {threadIdx.x, 2.0f};
  my_vec.push_back(temp);
  assert(my_vec.size() == 1);
  assert(my_vec.storage_ratio() >= 1);
  ss temp2 = my_vec[0];
  printf("threadIdx.x: %u, ss.x: %u, ss.y: %f\n", threadIdx.x, temp2.x, temp2.y);
  temp.y = 3.0f;
  my_vec[0].x = temp.x;
  my_vec[0].y = temp.y;
  ss temp3 = my_vec.pop_back();
  printf("threadIdx.x: %u, ss.x: %u, ss.y: %f\n", threadIdx.x, temp3.x, temp3.y);
  my_vec.clear();
  temp.x = 0;
  for (int i = 0; i < 10000; i++){
    my_vec.push_back(temp);
    temp.x++;}
  temp.x--;
  for (int i = 0; i < 10000; i++) {
    assert(my_vec.pop_back().x == temp.x);
    temp.x--;}
  cu_vec<ss> my_vec2(2);
  assert(my_vec2[1].x == 0);
  assert(my_vec2[1].y == 0.0f);
}

int main(){

  //default heap space is 8MB, if needed reserve more with:
  cudaDeviceSetLimit(cudaLimitMallocHeapSize, (1048576*32));
  test<<<1, 4>>>();
  cudaDeviceSynchronize();
}
$ nvcc -std=c++11 -o t376 t376.cu
$ cuda-memcheck ./t376
========= CUDA-MEMCHECK
threadIdx.x: 0, ss.x: 0, ss.y: 2.000000
threadIdx.x: 1, ss.x: 1, ss.y: 2.000000
threadIdx.x: 2, ss.x: 2, ss.y: 2.000000
threadIdx.x: 3, ss.x: 3, ss.y: 2.000000
threadIdx.x: 0, ss.x: 0, ss.y: 3.000000
threadIdx.x: 1, ss.x: 1, ss.y: 3.000000
threadIdx.x: 2, ss.x: 2, ss.y: 3.000000
threadIdx.x: 3, ss.x: 3, ss.y: 3.000000
========= ERROR SUMMARY: 0 errors
$

除了您在此处看到的代码外,尚未对代码进行测试。