web-dev-qa-db-ja.com

CUDAデバイスへのポインターを含む構造体のコピー

ポインターを含む構造体で計算を行うためにCUDAデバイスが必要なプロジェクトに取り組んでいます。

typedef struct StructA {
    int* arr;
} StructA;

構造体にメモリを割り当ててからデバイスにコピーすると、構造体のみがコピーされ、ポインタの内容はコピーされません。現在、最初にポインターを割り当ててから、その新しいポインター(GPU上にある)を使用するようにHost構造体を設定することでこれを回避しています。次のコードサンプルは、上記の構造体を使用したこのアプローチを説明しています。

#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;
}

私の質問は:これはそれを行う方法ですか?

大変な作業のようですが、これは非常に単純な構造体であることを思い出してください。私の構造体に多くのポインターまたはポインター自体を持つ構造体が含まれている場合、割り当てとコピーのコードは非常に広範で混乱を招きます。

28

編集:CUDA 6ではユニファイドメモリが導入されているため、この「ディープコピー」の問題がはるかに簡単になります。詳細については、 この投稿 を参照してください。


can構造体を値でカーネルに渡すことを忘れないでください。このコードは機能します:

// 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;
24
harrism

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));
}

コメントされていないデストラクタを使用します(コードが実際に何をするかについてはあまり注意を払わないでください)。そのコードを実行すると、次の出力が表示されます

Calling destructor
Counting in the locked case: 512
Calling destructor
GPUassert: invalid device pointer D:/Project/passStructToKernel/passClassToKernel/Utilities.cu 37

次に、デストラクタへの2つの呼び出しがあります。1つはカーネル出口で、もう1つはメイン出口でです。エラーメッセージは、d_stateが指すメモリ位置がカーネル出口で解放された場合、メイン出口でそれ以上解放できないという事実に関連しています。したがって、デストラクタはホストとデバイスの実行で異なる必要があります。これは、上記のコードでコメント化されたデストラクタによって実現されます。

2
JackOLantern