Pointers 将包含指针的结构复制到CUDA设备

Pointers 将包含指针的结构复制到CUDA设备,pointers,struct,cuda,device,host,Pointers,Struct,Cuda,Device,Host,我正在做一个项目,需要我的CUDA设备在包含指针的结构上进行计算 typedef struct StructA { int* arr; } StructA; 当我为结构分配内存,然后将其复制到设备时,它只会复制结构,而不会复制指针的内容。现在我正在解决这个问题,首先分配指针,然后将主机结构设置为使用新指针(驻留在GPU上)。下面的代码示例使用上面的结构描述了此方法: #define N 10 int main() { int h_arr[N] = {1,2,3,4,5,6,

我正在做一个项目,需要我的CUDA设备在包含指针的结构上进行计算

typedef struct StructA {
    int* arr;
} StructA;
当我为结构分配内存,然后将其复制到设备时,它只会复制结构,而不会复制指针的内容。现在我正在解决这个问题,首先分配指针,然后将主机结构设置为使用新指针(驻留在GPU上)。下面的代码示例使用上面的结构描述了此方法:

#define N 10

int main() {

    int h_arr[N] = {1,2,3,4,5,6,7,8,9,10};
    StructA *h_a = (StructA*)malloc(sizeof(StructA));
    StructA *d_a;
    int *d_arr;

    // 1. Allocate device struct.
    cudaMalloc((void**) &d_a, sizeof(StructA));

    // 2. Allocate device pointer.
    cudaMalloc((void**) &(d_arr), sizeof(int)*N);

    // 3. Copy pointer content from host to device.
    cudaMemcpy(d_arr, h_arr, sizeof(int)*N, cudaMemcpyHostToDevice);

    // 4. Point to device pointer in host struct.
    h_a->arr = d_arr;

    // 5. Copy struct from host to device.
    cudaMemcpy(d_a, h_a, sizeof(StructA), cudaMemcpyHostToDevice);

    // 6. Call kernel.
    kernel<<<N,1>>>(d_a);

    // 7. Copy struct from device to host.
    cudaMemcpy(h_a, d_a, sizeof(StructA), cudaMemcpyDeviceToHost);

    // 8. Copy pointer from device to host.
    cudaMemcpy(h_arr, d_arr, sizeof(int)*N, cudaMemcpyDeviceToHost);

    // 9. Point to host pointer in host struct.
    h_a->arr = h_arr;
}
#定义N 10
int main(){
int h_arr[N]={1,2,3,4,5,6,7,8,9,10};
StructA*h_a=(StructA*)malloc(sizeof(StructA));
结构*d_a;
int*d_arr;
//1.分配设备结构。
cudamaloc((void**)和d_a,sizeof(StructA));
//2.分配设备指针。
cudamaloc((void**)和(d_arr),sizeof(int)*N;
//3.将指针内容从主机复制到设备。
cudaMemcpy(d_arr,h_arr,sizeof(int)*N,cudaMemcpyHostToDevice);
//4.指向主机结构中的设备指针。
h_a->arr=d_arr;
//5.将结构从主机复制到设备。
cudaMemcpy(d_a,h_a,sizeof(StructA),cudamemcpyhostodevice);
//6.调用内核。
内核(d_a);
//7.将结构从设备复制到主机。
cudaMemcpy(h_a、d_a、sizeof(StructA)、cudamemcpydevicetoost);
//8.将指针从设备复制到主机。
cudaMemcpy(h_arr,d_arr,sizeof(int)*N,cudaMemcpyDeviceToHost);
//9.指向主机结构中的主机指针。
h_a->arr=h_arr;
}
我的问题是:这样做吗?


这似乎是一个非常多的工作,我提醒你,这是一个非常简单的结构。如果我的结构包含大量指针或带有指针的结构,那么分配和复制的代码将非常广泛和混乱。

数组结构在cuda中是一场噩梦。您必须将每个指针复制到设备可以使用的新结构。也许你可以改为使用一个结构数组?如果不是的话,我找到的唯一方法就是像你那样攻击它,这一点都不漂亮

编辑: 因为我不能在上面的帖子上发表评论:第9步是多余的,因为你可以将第8步和第9步改为

// 8. Copy pointer from device to host.
cudaMemcpy(h->arr, d_arr, sizeof(int)*N, cudaMemcpyDeviceToHost);

编辑:CUDA 6引入了统一内存,这使得“深度复制”问题变得更加容易。有关更多详细信息,请参阅


不要忘记,您可以通过值将结构传递给内核。此代码适用于:

// pass struct by value (may not be efficient for complex structures)
__global__ void kernel2(StructA in)
{
    in.arr[threadIdx.x] *= 2;
}
这样做意味着您只需将阵列复制到设备,而不必复制结构:

int h_arr[N] = {1,2,3,4,5,6,7,8,9,10};
StructA h_a;
int *d_arr;

// 1. Allocate device array.
cudaMalloc((void**) &(d_arr), sizeof(int)*N);

// 2. Copy array contents from host to device.
cudaMemcpy(d_arr, h_arr, sizeof(int)*N, cudaMemcpyHostToDevice);

// 3. Point to device pointer in host struct.
h_a.arr = d_arr;

// 4. Call kernel with host struct as argument
kernel2<<<N,1>>>(h_a);

// 5. Copy pointer from device to host.
cudaMemcpy(h_arr, d_arr, sizeof(int)*N, cudaMemcpyDeviceToHost);

// 6. Point to host pointer in host struct 
//    (or do something else with it if this is not needed)
h_a.arr = h_arr;
int h_arr[N]={1,2,3,4,5,6,7,8,9,10};
结构h_a;
int*d_arr;
// 1. 分配设备阵列。
cudamaloc((void**)和(d_arr),sizeof(int)*N;
// 2. 将阵列内容从主机复制到设备。
cudaMemcpy(d_arr,h_arr,sizeof(int)*N,cudaMemcpyHostToDevice);
// 3. 指向主机结构中的设备指针。
h_a.arr=d_arr;
// 4. 以主机结构作为参数调用内核
内核2(h_a);
// 5. 将指针从设备复制到主机。
cudaMemcpy(h_arr,d_arr,sizeof(int)*N,cudaMemcpyDeviceToHost);
// 6. 指向主机结构中的主机指针
//(如果不需要,也可以用它做其他事情)
h_a.arr=h_arr;

正如Mark Harris指出的,结构可以通过值传递给CUDA内核。但是,应该注意设置一个适当的析构函数,因为析构函数是在内核退出时调用的

考虑下面的例子

#include <stdio.h>

#include "Utilities.cuh"

#define NUMBLOCKS  512
#define NUMTHREADS 512 * 2

/***************/
/* TEST STRUCT */
/***************/
struct Lock {

    int *d_state;

    // --- Constructor
    Lock(void) {
        int h_state = 0;                                        // --- Host side lock state initializer
        gpuErrchk(cudaMalloc((void **)&d_state, sizeof(int)));  // --- Allocate device side lock state
        gpuErrchk(cudaMemcpy(d_state, &h_state, sizeof(int), cudaMemcpyHostToDevice)); // --- Initialize device side lock state
    }

    // --- Destructor (wrong version)
    //~Lock(void) { 
    //  printf("Calling destructor\n");
    //  gpuErrchk(cudaFree(d_state)); 
    //}

    // --- Destructor (correct version)
//  __host__ __device__ ~Lock(void) {
//#if !defined(__CUDACC__)
//      gpuErrchk(cudaFree(d_state));
//#else
//
//#endif
//  }

    // --- Lock function
    __device__ void lock(void) { while (atomicCAS(d_state, 0, 1) != 0); }

    // --- Unlock function
    __device__ void unlock(void) { atomicExch(d_state, 0); }
};

/**********************************/
/* BLOCK COUNTER KERNEL WITH LOCK */
/**********************************/
__global__ void blockCounterLocked(Lock lock, int *nblocks) {

    if (threadIdx.x == 0) {
        lock.lock();
        *nblocks = *nblocks + 1;
        lock.unlock();
    }
}

/********/
/* MAIN */
/********/
int main(){

    int h_counting, *d_counting;
    Lock lock;

    gpuErrchk(cudaMalloc(&d_counting, sizeof(int)));

    // --- Locked case
    h_counting = 0;
    gpuErrchk(cudaMemcpy(d_counting, &h_counting, sizeof(int), cudaMemcpyHostToDevice));

    blockCounterLocked << <NUMBLOCKS, NUMTHREADS >> >(lock, d_counting);
    gpuErrchk(cudaPeekAtLastError());
    gpuErrchk(cudaDeviceSynchronize());

    gpuErrchk(cudaMemcpy(&h_counting, d_counting, sizeof(int), cudaMemcpyDeviceToHost));
    printf("Counting in the locked case: %i\n", h_counting);

    gpuErrchk(cudaFree(d_counting));
}

然后有两个对析构函数的调用,一个在内核出口,一个在主出口。错误消息与这样一个事实有关:如果
d_state
所指向的内存位置在内核出口处被释放,那么它们在主出口处就不能再被释放。因此,对于主机和设备执行,析构函数必须不同。这是由上面代码中注释的析构函数完成的。

步骤7和9是冗余的,但在其他方面,这就是它的基本原理。正如下面的答案所说,最好避免GPU上复杂的、基于指针的数据结构。GPU上的性能更差,API也不是专门为它设计的。我可以看出第7步是多余的,但为什么第9步?嗯
h_a
是(或应该是)主机内存中保存的设备结构的“映像”。根据您的真实意图,指定它在主机内存中保存指针可能是坏习惯/错误/设备内存泄漏的组合。在您将
d_a
的内容复制回
h_a
后,您已经“完成了一个完整的循环”,回到了开始的位置。但是为了将结构正确复制到设备,我必须将
h_a
的指针设置为
d_arr
(步骤4)。因此,当我复制回数据时,我还必须将
h_a
中的指针设置为我刚才复制到的数组。我同意在我上面的示例中,步骤7是多余的,因为结构中没有其他信息,但如果有,该步骤就不会是多余的。。还是我完全弄错了?谢谢你,tahatmat,为我们提供了在主机和设备内存之间来回复制结构的模式。不过,我认为值得一提的是第二种方法,它似乎更为一致,有助于避免执行步骤9。函数cudaMemcpy()的具体功能实际上允许以这样的方式取消主机代码中的设备指针:跳过步骤4,在步骤5中将h_a复制到d_a之后,手动将每个设备指针地址复制到d_a,如下所示:cudaMemcpy(&(d_a->arr),&(d_arr),sizeof(int*),cudaMemcpyHostToDevice)。同样,“d_a->arr”是合法的。首先,这个答案是危险的,因为它违背了并行计算中关于AOS/SOA的标准智慧。在所有并行计算中,包括带有SSE/AVX指令集的多核CPU,阵列结构(SOA)比结构阵列(AO)更可取。原因是SOA跨线程维护引用的局部性(例如,d_a.arr的相邻元素由运行co的相邻线程访问)
Calling destructor
Counting in the locked case: 512
Calling destructor
GPUassert: invalid device pointer D:/Project/passStructToKernel/passClassToKernel/Utilities.cu 37