2012-11-20 5 views
19

커널 함수 내에 일부 배열을 동적으로 할당해야합니다. 내가 어떻게 그럴 수 있니?커널 내부에 동적으로 배열을 할당하는 방법은 무엇입니까?

내 코드는 같은 것입니다 :

__global__ func(float *grid_d,int n, int nn){ 
    int i,j; 
    float x[n],y[nn]; 
    //Do some really cool and heavy computations here that takes hours. 
} 

그러나 그것은 작동하지 않습니다. 이것이 호스트 코드 안에 있다면 malloc을 사용할 수 있습니다. cudaMalloc은 호스트에 포인터가 필요하고 다른 장치에는 포인터가 필요합니다. 커널 함수 안에는 호스트 포인터가 없습니다.

그럼 어떻게해야합니까?

만약 모든 어레이를 할당하는 데 너무 오래 걸리는 경우 (크기 n과 크기 nn 중 약 4 개가 필요함)이 문제는 발생하지 않습니다. 커널은 아마도 적어도 20 분 동안 작동 할 것입니다.

+2

[동적 메모리 할당] (http://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#dynamic-global-memory-allocation-and) 섹션을 읽는 것이 좋습니다. (CUDA C 프로그래머 가이드)의 장치 코드 (http://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#dynamic-global-memory-allocation-and-operations). 이 기능을 사용하려면 GPU에서 2.0 이상의 컴퓨팅 기능이 필요합니다. –

+0

이 커널을 실행할 구성 (블록, 스레드)은 무엇입니까? 'n'과 'nn'의 전형적인 범위는 무엇입니까 (작은 크기의 경우이를 레지스터 나 공유 메모리로 집어 넣을 수 있습니다). –

답변

25

동적 메모리 할당은 컴퓨팅 기능 2.x 이상에서만 지원됩니다. 당신은 당신의 예제가 될 수 있도록 커널에서 C++ 새로운 키워드 또는 malloc에 ​​하나를 사용할 수 있습니다

__global__ func(float *grid_d,int n, int nn){ 
    int i,j; 
    float *x = new float[n], *y = new float[nn]; 
} 

이 문맥의 수명을 가지고 로컬 메모리 런타임 힙에 메모리를 할당, 그래서 당신이 무료 확인 메모리를 다시 사용하지 않으려는 경우 커널이 실행을 마친 후 메모리. 또한 런타임 힙 메모리는 호스트 API에서 직접 액세스 할 수 없으므로 예를 들어 커널 내부에 할당 된 포인터를 인수로 cudaMemcpy에 전달할 수 없습니다.

+0

동적으로 배열을 할당해야하는 비슷한 상황이 있습니다. 이러한 배열은 모든 스레드가 쓰기 목적으로 액세스해야합니다. 커널의 크기가 (1,4) 즉 nThreads = 4이고 nBlocks = 1 인 경우 커널 내부에이 동적 할당 프로세스를 선언하면 4 배의 배열을 생성한다는 것을 혼란스럽게 생각합니다. – skm

+0

여기에 '자유'가 적절합니까? 커널 내부에서 로컬 힙을 해제 할 수있는 다른 함수가 있습니까? – landau

+1

@landau 아니요 그냥 무료로 사용하거나 삭제하십시오. – talonmies

10

@talonmies가 커널 내에서 동적으로 메모리를 할당하는 방법에 대한 질문에 답변했습니다. 이는 보충 답변으로, __device__ malloc()의 성능 및 고려해야 할 대안에 대해 설명합니다.

커널에서 동적으로 메모리를 할당하면 GPU 코드가 CPU 코드와 비슷하게 보이기 때문에 유혹을받을 수 있습니다. 그러나 성능에 심각한 영향을 줄 수 있습니다. 필자는 자체 시험을 작성하여 아래에 포함 시켰습니다. 이 테스트는 약 260 만 개의 스레드를 시작합니다. 각 스레드는 전역 메모리의 16 정수를 스레드 인덱스에서 파생 된 일부 값으로 채운 다음 값을 합하여 합계를 반환합니다.

이 테스트에서는 두 가지 접근 방식을 구현합니다. 첫 번째 방법은 __device__ malloc()을 사용하고 두 번째 방법은 커널을 실행하기 전에 할당 된 메모리를 사용합니다.

내 2.0 장치에서 커널은 미리 할당 된 메모리를 사용할 때 __device__ malloc() 및 27ms를 사용할 때 1500ms에서 실행됩니다. 즉, 메모리가 커널 내에서 동적으로 할당 될 때 이 56x 길어질 때이 실행됩니다. 이 시간에는 커널의 일부가 아닌 외부 루프 cudaMalloc()/cudaFree()이 포함됩니다. 종종 같은 수의 동일한 스레드로 같은 커널을 여러 번 실행하면 cudaMalloc()/cudaFree()의 비용이 모든 커널 시작에 대해 상각됩니다. 그 차이는 약 60 배까지 훨씬 커집니다.

추측 해 보면 성능 ​​저하는 부분적으로 암시 적 직렬화에 의한 것이라고 생각합니다. GPU는 각 호출자에게 별도의 메모리 청크를 제공하기 위해 모든 동시 호출을 __device__ malloc()으로 직렬화해야합니다.

__device__ malloc()을 사용하지 않는 버전은 커널을 실행하기 전에 모든 GPU 메모리를 할당합니다. 메모리에 대한 포인터가 커널에 전달됩니다. 각 스레드는 __device__ malloc()을 사용하는 대신 이전에 할당 된 메모리에 대한 인덱스를 계산합니다.

메모리를 할당 할 때 잠재적 인 문제는 일부 스레드 만 메모리를 할당해야하며 스레드가 어떤 스레드인지 알 수없는 경우 모든 스레드에 메모리를 할당해야한다는 것입니다. 충분한 메모리가 없으면 커널 호출 당 스레드 수를 줄이고 __device__ malloc()을 사용하는 것이 더 효율적일 수 있습니다. 다른 해결 방법은 아마도 __device__ malloc()이 백그라운드에서 수행중인 작업을 다시 구현하는 결과를 가져오고 비슷한 성능 저하를 보게됩니다.

테스트 __device__ malloc()의 성능 :

#include "cuda_runtime.h" 
#include "device_launch_parameters.h" 
#include <stdio.h> 

const int N_ITEMS(16); 

#define USE_DYNAMIC_MALLOC 

__global__ void test_malloc(int* totals) 
{ 
    int tx(blockIdx.x * blockDim.x + threadIdx.x); 

    int* s(new int[N_ITEMS]); 

    for (int i(0); i < N_ITEMS; ++i) { 
    s[i] = tx * i; 
    } 

    int total(0); 
    for (int i(0); i < N_ITEMS; ++i) { 
    total += s[i]; 
    } 

    totals[tx] = total; 

    delete[] s; 
} 

__global__ void test_malloc_2(int* items, int* totals) 
{ 
    int tx(blockIdx.x * blockDim.x + threadIdx.x); 

    int* s(items + tx * N_ITEMS); 

    for (int i(0); i < N_ITEMS; ++i) { 
    s[i] = tx * i; 
    } 

    int total(0); 
    for (int i(0); i < N_ITEMS; ++i) { 
    total += s[i]; 
    } 

    totals[tx] = total; 
} 

int main() 
{ 
    cudaError_t cuda_status; 

    cudaSetDevice(0); 

    int blocks_per_launch(1024 * 10); 
    int threads_per_block(256); 

    int threads_per_launch(blocks_per_launch * threads_per_block); 

    int* totals_d; 
    cudaMalloc((void**)&totals_d, threads_per_launch * sizeof(int)); 

    cudaEvent_t start, stop; 
    cudaEventCreate(&start); 
    cudaEventCreate(&stop); 

    cudaDeviceSynchronize(); 
    cudaEventRecord(start, 0); 

#ifdef USE_DYNAMIC_MALLOC 
    cudaDeviceSetLimit(cudaLimitMallocHeapSize, threads_per_launch * N_ITEMS * sizeof(int)); 

    test_malloc<<<blocks_per_launch, threads_per_block>>>(totals_d); 
#else 
    int* items_d; 
    cudaMalloc((void**)&items_d, threads_per_launch * sizeof(int) * N_ITEMS); 

    test_malloc_2<<<blocks_per_launch, threads_per_block>>>(items_d, totals_d); 

    cudaFree(items_d); 
#endif 

    cuda_status = cudaDeviceSynchronize(); 
    if (cuda_status != cudaSuccess) { 
    printf("Error: %d\n", cuda_status); 
    exit(1); 
    } 

    cudaEventRecord(stop, 0); 
    cudaEventSynchronize(stop); 
    float elapsedTime; 
    cudaEventElapsedTime(&elapsedTime, start, stop); 

    printf("Elapsed: %f\n", elapsedTime); 

    int* totals_h(new int[threads_per_launch]); 
    cuda_status = cudaMemcpy(totals_h, totals_d, threads_per_launch * sizeof(int), cudaMemcpyDeviceToHost); 
    if (cuda_status != cudaSuccess) { 
    printf("Error: %d\n", cuda_status); 
    exit(1); 
    } 

    for (int i(0); i < 10; ++i) { 
    printf("%d ", totals_h[i]); 
    } 
    printf("\n"); 

    cudaFree(totals_d); 
    delete[] totals_h; 

    return cuda_status; 
} 

출력 : N의 값과 윈이 알려진다면

C:\rd\projects\test_cuda_malloc\Release>test_cuda_malloc.exe 
Elapsed: 27.311169 
0 120 240 360 480 600 720 840 960 1080 

C:\rd\projects\test_cuda_malloc\Release>test_cuda_malloc.exe 
Elapsed: 1516.711914 
0 120 240 360 480 600 720 840 960 1080 
+1

두 번째 테스트에서 cudaMalloc을 실행해야합니다. 그렇지 않으면 차고 (첫 번째 테스트)에서 정지 된 차에 달릴 준비가 된 차 (두 번째 테스트)를 비교하고 있습니다. 두 커널 모두 동일한 저장 영역 요구 사항이 필요합니다. – pQB

+0

pQB 이의 이외에 :'cudaMalloc'은 하나의 커다란 배열을 할당하는데, 이것은 2.5million의 작은 매트릭스 (각 쓰레드 하나당)의 할당과 비교됩니다. 이러한 절차는 물론 느려지고 CPU에 대한 테스트 결과에 따르면보고 된 60x 속도 저하는 실제로 좋은 작업입니다 (코드가 segfault를 제공하지 않으면 할당자가 너무 많은 행렬을 처리해야 함). 공정한 테스트는 다음과 같습니다. 동일한 배열을 할당합니다. (1)'커널 <<<1,1> >>에 대해 (1)'cudaMalloc', (2)에 할당하십시오. 나는'커널'할당이 ~ 3 번 느린 것을 본다. 이것이 진정한 성과입니다. –

+0

@pQB : 고마워요. 측정 할 수 없다고 가정하고 타이밍에서 cudaMalloc()을 벗어났습니다. 놀랍게도, 그것을 추가하면 60x에서 56x로 변경됩니다. 나는 대답을 업데이트하고 cudaMalloc()/cudaFree()를 타이밍에 포함시키는 의미에 대한 설명을 추가했습니다. –

2

커널을 호출하기 전에, 왜 호스트 측의 메모리를 cudaMalloc하지 그리고 장치 메모리 포인터를 커널에 건네 줍니까?

+0

각 커널은 하나의 배열을 소유해야하기 때문에. – Granada

+0

여러 개의 케넬을 동시에 시작 하시겠습니까? 충분한 공간을 할당 할 수 없으며 각 커널은 그 중 일부만 공유합니다. –

+0

만약 내가, 예를 들어, 1000 커널과 내가 10 크기의 배열을 필요로 lauch. 나는 크기 n * 1000의 10 개의 배열을 만들어야합니까? 그리고 threadid와 blockid를 사용하여 커널 전체에서 이것을 공유 할 수 있습니까? – Granada

관련 문제