Я думаю, что ваше впечатление 1 может быть реализовано примерно так, как описано здесь (стек для каждого потока, предварительно выделенный). У него есть проблемы, которые вы упоминаете, связанные с перераспределением. В более новых графических процессорах память размером в несколько гигабайт (или больше) является обычной, поэтому проблема перераспределения может быть не очень серьезной, если общий объем памяти не является проблемой.
Я думаю, что ваше впечатление 2 может быть реализовано примерно так, как описано здесь (потокобезопасный векторный push-back для всего устройства). У него есть проблемы, о которых вы упомянули, связанные с отсутствием упорядочения результатов в векторе результатов. Возможно, их можно решить с помощью операции сортировки после завершения операции сбора.
(4. Похоже, у вас, вероятно, уже есть представление о том, как произвести «худшее» впечатление 4.)
- Это оставляет впечатление 3. Мы могли бы использовать идею, которая представляет собой комбинацию впечатления 1 и впечатления 2, то есть создать вектор push_back для каждого потока, но использовать распределение по требованию через ядро
malloc
или * 1018. *. Распределение памяти в ядре, как это довольно медленно, и не без собственных проблем (например, вам может потребоваться зарезервировать дополнительное пространство кучи, выделенная память кучи в ядре не может участвовать в передаче на хост, небольшие выделения могут быть неэффективными в памяти использование), но на самом деле нет никакого способа определить, какой из этих подходов может быть наилучшим, без дополнительной информации о размерах вашей проблемы. Если отслеживание родительских узлов является относительно редкой операцией при обходе графа, подход с динамическим распределением может не быть проблемой.
Вот пример того, как можно создать простой вектор (для потока):
$ 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
$
Код не был проверен больше, чем вы видите здесь.