2012-02-16 34 views
24

Tôi đang làm việc trên một dự án mà tôi cần thiết bị CUDA của mình để thực hiện tính toán trên cấu trúc chứa con trỏ.Sao chép một cấu trúc có chứa con trỏ đến thiết bị CUDA

Khi tôi cấp phát bộ nhớ cho cấu trúc và sau đó sao chép bộ nhớ vào thiết bị, nó sẽ chỉ sao chép cấu trúc chứ không phải nội dung của con trỏ. Bây giờ tôi đang làm việc xung quanh điều này bằng cách phân bổ con trỏ đầu tiên, sau đó thiết lập cấu trúc máy chủ để sử dụng con trỏ mới (mà nằm trên GPU). Mẫu mã sau đây mô tả phương pháp này sử dụng các cấu trúc từ trên cao:

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

Câu hỏi của tôi là: Đây có phải là cách để làm điều đó?

Dường như có rất nhiều công việc và tôi nhắc bạn rằng đây là cấu trúc rất đơn giản. Nếu cấu trúc của tôi chứa rất nhiều con trỏ hoặc cấu trúc với con trỏ, mã để phân bổ và sao chép sẽ khá rộng và khó hiểu.

+2

Các bước 7 và 9 là thừa, nhưng nếu không thì đó là khá nhiều.Như câu trả lời dưới đây, bạn được phục vụ tốt nhất bằng cách tránh các cấu trúc dữ liệu phức tạp, dựa trên con trỏ trên GPU. Hiệu suất là trên GPU là tồi tệ hơn, và các API thực sự không được thiết kế cho nó. – talonmies

+0

Tôi có thể thấy rằng bước 7 là thừa, nhưng tại sao bước 9? –

+0

cũng 'h_a' là (hoặc phải là) một" hình ảnh "của cấu trúc thiết bị được giữ trong bộ nhớ máy chủ. Gán nó để giữ một con trỏ trong bộ nhớ máy chủ có lẽ là một sự kết hợp của thực hành xấu/sai/rò rỉ bộ nhớ thiết bị tùy thuộc vào ý định thực sự của bạn là gì. Sau khi bạn đã sao chép nội dung của 'd_a' trở lại' h_a' bạn có "đến vòng tròn đầy đủ" và quay lại nơi bạn bắt đầu. – talonmies

Trả lời

22

Edit: CUDA 6 giới thiệu Unified Memory, mà làm cho vấn đề "bản sao sâu" này dễ dàng hơn rất nhiều. Xem this post để biết thêm chi tiết.


Đừng quên rằng bạn có thể cấu trúc đi qua giá trị cho hạt nhân. Mã này hoạt động:

// pass struct by value (may not be efficient for complex structures) 
__global__ void kernel2(StructA in) 
{ 
    in.arr[threadIdx.x] *= 2; 
} 

Làm như vậy có nghĩa là bạn chỉ cần sao chép mảng đến thiết bị, không phải là cấu trúc:

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; 
-3

cấu trúc của mảng là một cơn ác mộng trong cuda. Bạn sẽ phải sao chép từng con trỏ vào một cấu trúc mới mà thiết bị có thể sử dụng. Có lẽ bạn thay vào đó có thể sử dụng một mảng các cấu trúc? Nếu không phải cách duy nhất tôi tìm thấy là tấn công nó theo cách bạn làm, điều đó hoàn toàn không đẹp.

EDIT: kể từ khi tôi không thể đưa ra ý kiến ​​về bài đầu: Bước 9 là không cần thiết, vì bạn có thể thay đổi bước 8 và 9 vào

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

Đầu tiên, câu trả lời này là nguy hiểm vì nó đi ngược lại sự khôn ngoan tiêu chuẩn về AOS/SOA trong tính toán song song. Cấu trúc của mảng (SOA) là thích hợp hơn trên mảng cấu trúc (AOS) trong tất cả các tính toán song song, bao gồm các CPU đa lõi với các bộ lệnh SSE/AVX. Lý do là SOA duy trì vị trí tham chiếu trên các luồng (ví dụ: các phần tử lân cận của d_a.arr được truy cập bởi các luồng lân cận đang chạy đồng thời). Một cấu trúc với một con trỏ trong nó không giống với cấu trúc của mảng. Thứ hai, bạn có thể đơn giản hóa mã này bằng cách chuyển cấu trúc theo giá trị. – harrism

+1

@harrism Tại sao Array of Structs không thích hợp hơn trong cuda? Tôi không hiểu điều này, bạn có thể cho tôi một ví dụ hoặc một liên kết? Cảm ơn – BugShotGG

+0

@GeoPapas [tại đây] (http://stackoverflow.com/questions/18136785/kernel-using-aos-is-faster-than-using-soa/18137311#18137311) là câu hỏi/câu trả lời thảo luận về SOA so với AOS với các ví dụ. –

1

Như đã chỉ ra Mark Harris, cấu trúc có thể được thông qua bởi các giá trị để CUDA hạt nhân. Tuy nhiên, một số cần được dành để thiết lập một destructor thích hợp kể từ khi destructor được gọi là lúc thoát khỏi hạt nhân.

Hãy xem xét ví dụ sau

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

với destructor không chú thích (không phải trả quá nhiều sự chú ý vào những gì mã thực sự thực hiện). Nếu bạn chạy mã đó, bạn sẽ nhận được kết quả sau đây

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

Sau đó, có hai cuộc gọi đến trình hủy, một lần tại lối ra hạt nhân và một lần tại lối ra chính. Thông báo lỗi có liên quan đến thực tế là, nếu các vị trí bộ nhớ được trỏ đến bởi d_state được giải phóng tại lối ra hạt nhân, chúng không thể được giải phóng nữa tại lối ra chính. Theo đó, destructor phải khác nhau đối với các máy chủ và các thiết bị thực thi. Điều này được thực hiện bởi các destructor nhận xét trong đoạn mã trên.

Các vấn đề liên quan