2013-02-27 1 views
3

CUDA에서 구현할 때 내 1D 파동 방정식이 C/C++보다 느립니다. 아무도 내가 잘못한 것을 말해 줄 수 있습니까? 여기 내 코드는 다음과 같습니다.헬름홀츠 방정식을위한 CUDA의 1D 유한 차분 시간 영역 (FDTD)

__global__ void Solver1d(float* up, float* u, float* um) 
{ 
    int id; 
    float dx,dt; 
    dx = (float)L/n; 
    dt = (float)dx/c; 
    float r= c*((float)dt/dx); 
    float R = r*r; 

    // index mapping between data and threads 
    id = threadIdx.x + blockIdx.x*blockDim.x; 

    // Allowing all threads in the range of valids data to execute 
    if (id<n) 
    { 
     if(id==0) 
     { 
      up[id]=0; 
     } 
     else if(id==n-1) 
     { 
      up[n-1]=0; 
     } 
     else 
     { 
      up[id] = 2*u[id]-um[id]+R*(u[id+1]-2*u[id]+u[id-1]);  
     } 
    } 
} 

// main program 
int main(int argc, char *argv[]) 
{   

    // declare all variables 
    int i; 
    float inner,L2_exact,ue[n],dx,dt; 
    dx = (float)L/n; 
    dt = (float)(0.05*dx/c); // Max time step 
    float r= c*((float)dt/dx); 
    float R = r*r; 

    // Allocate memory on host 
    //float u=(float *)malloc((n)*sizeof(float)); 
    //float um=(float *)malloc((n)*sizeof(float)); 
    float up[n],um[n],u[n]; 


    //Pointers for device memory allocation 
    float *dev_up, *dev_u, *dev_um; 


    // Allocating memory to device (GPU) 
    HANDLE_ERROR(cudaMalloc((void**)&dev_up, n*sizeof(float))); 
    HANDLE_ERROR(cudaMalloc((void**)&dev_u, n*sizeof(float))); 
    HANDLE_ERROR(cudaMalloc((void**)&dev_um, n*sizeof(float))); 


    cudaEvent_t start, stop; 
    float elapsedTime; 

    // Start timer 
    HANDLE_ERROR(cudaEventCreate(&start)); 
    HANDLE_ERROR(cudaEventCreate(&stop)); 
    HANDLE_ERROR(cudaEventRecord(start,0)); 

    //Initialize the stream 
    cudaStream_t stream; 
    HANDLE_ERROR(cudaStreamCreate(&stream)); 
    //Initial condition 
    for(i=0;i<n;i++) 
    { 
     u[i]=sin(2*PI*i*dx); 
     //printf("Initialization ok\n"); 
    } 

    // Enforcing special formula for t = -1 
    for(i=1;i<n-1 ;i++) 
    { 
     um[0] = 0; 
     um[n-1] = 0; 
     um[i] = u[i] + 0.5*R*(u[i-1] - 2*u[i] + u[i+1]); //+ 0.5*dt*dt*f(i*dx,t) 
     //printf("um is runing fine\n"); 
    } 

    // setting blocks and threads numbers 
    int noThreads=128; 
    dim3 dimBlock(noThreads,1,1); 
    dim3 dimGrid(1+n/(noThreads-1),1,1); 

    // move u and um to GPU 
    HANDLE_ERROR(cudaMemcpy(dev_u, u, n*sizeof(float), cudaMemcpyHostToDevice)); 
    HANDLE_ERROR(cudaMemcpy(dev_um, um, n*sizeof(float), cudaMemcpyHostToDevice)); 

    float t=0; 

    //int counter=0; 
    while(t<=T) 
    { 
     //counter++; 
     t += dt; 

     Solver1d<<<dimGrid,dimBlock>>>(dev_up,dev_u,dev_um); 
     // cudaDeviceSynchronize(); 
     for(i=0;i<n;i++) 
     { 
      um[i] = u[i]; 
      u[i] = up[i]; 
     } 

    } 

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

    printf("elapsed time: %lf sec\n",elapsedTime/1000); 
    // move the solution up from GPU to CPU 
    HANDLE_ERROR(cudaMemcpy(up, dev_up, n*sizeof(float), cudaMemcpyDeviceToHost)); 
    HANDLE_ERROR(cudaMemcpy(u, dev_u, n*sizeof(float), cudaMemcpyDeviceToHost)); 
    HANDLE_ERROR(cudaMemcpy(um, dev_um, n*sizeof(float), cudaMemcpyDeviceToHost)); 

    int j; 
    float L2cpuSolution=0.0; 
    float L2gpuSolution=0.0; 
    float ERROR_PERCENTAGE=0.0; 


    // Verification with exact solution 
    for(j=0;j<(n);j++) 
    { 
     //printf("up[%d]=%.12g\n",j,up[j]); 
     ue[j]=0.5*(sin(2*PI*(j*dx+c*T))+sin(2*PI*(j*dx-c*T))); 
     //printf("um[%d]=%.12g\n",j,um[j]); 
     inner += (ue[j]-up[j])*(ue[j]-up[j]); 
     L2cpuSolution += ue[j]*ue[j]; 
     L2gpuSolution += up[j]*up[j]; 

    } 
    L2cpuSolution = sqrt(L2cpuSolution)/n; 
    L2gpuSolution = sqrt(L2gpuSolution)/n; 
    L2_exact = sqrt(inner/(n)); 
    ERROR_PERCENTAGE = 100*(L2_exact/L2cpuSolution); 
    printf("L2_exact=%lf\n",L2_exact); 
    printf("gpul2=%lf, and cpuL2=%lf \n",L2gpuSolution,L2cpuSolution); 
    printf("ERROR_PERCENTAGE= %lf\n", ERROR_PERCENTAGE); 

    // Free device memory 
    cudaFree(dev_up); 
    cudaFree(dev_u); 
    cudaFree(dev_um); 

    return 0; 

} 
+0

여기서 'n'은 정의 되었습니까? 그 가치는 어느 것입니까? CPU 버전을 어떻게 지내셨습니까? GPU 시간 내에 'Initial condition'과 'Enforcing special formula'도 고려하고 있음에 유의하십시오. – pQB

+0

또한 while 루프 내에 호스트 배열 um과 u를 업데이트하는 루프가 있으며 나중에 컨텐츠를 cudaMemcpy 호출로 대체합니다. 이 루프를 제거하고 다시 측정하십시오. – brano

+0

T, L, n, c의 값은 얼마입니까? 또한 스트림을 만들지 만 결코 사용하지 않으며 결코 파괴하지 않습니다. 스트림 생성을 제거합니다. – brano

답변

3

본질적으로, 나는 당신이 여기서 무엇인가 잘못하고 있다고 생각하지 않습니다. 그러나 CUDA가 당신을 위해서 마술을하고 CPU를 구현하는 것보다 더 빨리 로딩해야한다고 생각해서는 안됩니다. 특히 1-D 파동 방정식 (CPU 구현에서 실제로는 하나의 for-loop 임)과 같이 비교적 간단한 것은 현대 컴퓨터에서 그렇게 간단하여 병렬화 할 이유가 거의 없습니다. 왜냐하면 호스트에서 디바이스로 그리고 다시 데이터를 전송하는 것이 GPU 구현의 병목 현상이 될 수 있음을 기억하십시오. 따라서 데이터 크기가 방대하지 않으면 (n> 10^6 정도), 가치가 있다고 생각하지 않습니다.

그러나 커널의 코드를 향상시키는 한 가지 방법은 여러 변수를 미리 계산하는 것입니다. 변수 dx, dt, rR은 전체 시뮬레이션에서 일정한 것처럼 보이지만 각각의 작은 스레드마다 각 시간 단계에서 계산됩니다. 아마도 수백만, 수백만의 불필요한 계산 일 것입니다. 또한 배열의 데이터에 텍스처 메모리를 사용하면 대부분의 메모리 액세스가 각 블록의 동일한 인접 영역에서 발생하므로 속도가 향상 될 수 있습니다.

+1

답변에는 솔루션 도메인이 비교적 작고 응용 프로그램이 수행되지 않는다는 가정이 많이 있습니다 (예를 들어, FDTD 시뮬레이션과 같이 보입니다.) 1D 심이라 할지라도 문제의 크기 또는 런 길이가 클 경우 지나치게 많은 처리 능력이 필요할 수 있습니다. –

+0

사실입니다. 그러나 이러한 가정은 유한 차이 시뮬레이션이 CPU에서 GPU, 위에서 언급 한 다른 제안 및 GPU 유형과 함께 GPU에서 잘 수행되지 않는 주된 이유입니다. – Yellow

+1

당신의 요점을 봅니다. FDTD (구체적으로는 유한 요소 또는 수정 된 FDTD 알고리즘에 대한 경험이 거의 없으므로 의견이 적용되지 않을 수 있음)은 "당황 스럽지만 평행합니다." 내 첫 OpenCL 시도는 i7 2.6Ghz보다 GTX580에서 약 50 배 빠릅니다. GPU 기반 시뮬레이션을 사용하면 오버 헤드가 발생하지만 설치 시간을 보상하기에 충분한 프레임을 실행하고 호스트와 컴퓨팅 장치간에 데이터를 너무 자주 이동하지 않아도되므로 GPU 사용을 선호합니다. (BTW, +1 - 대단한 대답, 방금 불완전하다고 느꼈다.) –

2

위의 의견에서 가장 중요한 질문과 토론은 C/C++를 구현할 때 1D 유한 차이 시간 도메인 (FDTD) 방법이 더 빠를 수 있으며 CUDA에서 구현 될 때가 아니라 순차적 기계에서 실행되고 병렬 GPU에서 실행되는지 여부입니다 .

나는 아래의 코드로이 질문에 답하려고하고있다. 여기에는 C/C++ 및 CUDA의 전자기 애플리케이션을위한 1D FDTD 메소드의 구현이 포함됩니다. 이론 및 C/C++ 구현은 Understanding the Finite-Difference Time-Domain Method에서 가져옵니다 (프로그램 3.1 참조). CUDA 버전에는 전역 메모리 만 사용하는 것과 공유 메모리를 사용하는 두 가지 방법이 있습니다. 후자의 경우, 두 개의 다른 커널을 시작하여 자기장과 전기장 업데이트간에 동기화를 시행합니다.

충분히 큰 문제 (SIZE = 10000000)의 경우 GPU 버전이 실제로 CPU보다 빠릅니다. Kepler K20c 카드의 코드를 테스트 한 결과는 다음과 같습니다.

Shared Memory version 
CPU elapsed time = 3980.763 ms 
GPU elapsed time = 356.828 ms 

Global Memory version 
GPU elapsed time = 359.768 ms 

공유 메모리를 사용하는 버전은 시나리오를 개선하지 않습니다. 여기

코드이다

kernel.cu

/* 1D FDTD simulation with an additive source. */ 

#include <stdio.h> 
#include <stdlib.h> 
#include <math.h> 

#include "TimingCPU.h" 
#include "TimingGPU.cuh" 

#define BLOCKSIZE 512 
//#define DEBUG 

/*******************/ 
/* iDivUp FUNCTION */ 
/*******************/ 
int iDivUp(int a, int b) { return ((a % b) != 0) ? (a/b + 1) : (a/b); } 

/********************/ 
/* CUDA ERROR CHECK */ 
/********************/ 
#define gpuErrchk(ans) { gpuAssert((ans), __FILE__, __LINE__); } 
inline void gpuAssert(cudaError_t code, char *file, int line, bool abort=true) 
{ 
    if (code != cudaSuccess) 
    { 
     fprintf(stderr,"GPUassert: %s %s %d\n", cudaGetErrorString(code), file, line); 
     if (abort) exit(code); 
    } 
} 

/***********************************/ 
/* HOST-SIZE FIELD UPDATE FUNCTION */ 
/***********************************/ 
void updateHost(double *h_ez, double* h_hy, double imp0, double qTime, const int source, const int N) { 

    /* update magnetic field */ 
    for (int mm = 0; mm < N - 1; mm++) 
     h_hy[mm] = h_hy[mm] + (h_ez[mm + 1] - h_ez[mm])/imp0; 

    /* update electric field */ 
    for (int mm = 1; mm < N; mm++) 
     h_ez[mm] = h_ez[mm] + (h_hy[mm] - h_hy[mm - 1]) * imp0; 

    /* use additive source at node 50 */ 
    h_ez[source] += exp(-(qTime - 30.) * (qTime - 30.)/100.); 

} 

/********************************************************/ 
/* DEVICE-SIZE FIELD UPDATE FUNCTION - NO SHARED MEMORY */ 
/********************************************************/ 
__global__ void updateDevice_v0(double *d_ez, double* d_hy, double imp0, double qTime, const int source, const int N) { 

    const int tid = threadIdx.x + blockIdx.x * blockDim.x; 

    /* update magnetic field */ 
    if (tid < N-1) d_hy[tid] = d_hy[tid] + (d_ez[tid + 1] - d_ez[tid])/imp0; 

    __threadfence(); 

    /* update electric field */ 
    if ((tid < N)&&(tid > 0)) d_ez[tid] = d_ez[tid] + (d_hy[tid] - d_hy[tid - 1]) * imp0; 

    /* use additive source at node 50 */ 
    if (tid == source) d_ez[tid] += exp(-(qTime - 30.) * (qTime - 30.)/100.); 

} 

/**************************************************************/ 
/* DEVICE-SIZE MAGNETIC FIELD UPDATE FUNCTION - SHARED MEMORY */ 
/**************************************************************/ 
__global__ void updateDevice_hy(double *d_ez, double* d_hy, double imp0, double qTime, const int source, const int N) { 

    const int tid = threadIdx.x + blockIdx.x * blockDim.x; 

    __shared__ double hy_temp[BLOCKSIZE+1], ez_temp[BLOCKSIZE+1]; 

    hy_temp[threadIdx.x] = d_hy[tid]; 
    ez_temp[threadIdx.x] = d_ez[tid]; 

    if ((threadIdx.x == 0)&&((tid + BLOCKSIZE) < N)) { 
     ez_temp[BLOCKSIZE] = d_ez[tid + BLOCKSIZE]; 
     hy_temp[BLOCKSIZE] = d_hy[tid + BLOCKSIZE]; 
    } 

    __syncthreads(); 

    /* update magnetic field */ 
    if (tid < N-1) d_hy[tid] = hy_temp[threadIdx.x] + (ez_temp[threadIdx.x + 1] - ez_temp[threadIdx.x])/imp0; 

} 

/**************************************************************/ 
/* DEVICE-SIZE ELECTRIC FIELD UPDATE FUNCTION - SHARED MEMORY */ 
/**************************************************************/ 
__global__ void updateDevice_ez(double *d_ez, double* d_hy, double imp0, double qTime, const int source, const int N) { 

    const int tid = threadIdx.x + blockIdx.x * blockDim.x; 

    __shared__ double hy_temp[BLOCKSIZE+1], ez_temp[BLOCKSIZE+1]; 

    hy_temp[threadIdx.x + 1] = d_hy[tid]; 
    ez_temp[threadIdx.x + 1] = d_ez[tid]; 

    if ((threadIdx.x == 0)&&(tid >= 1)) { 
     ez_temp[0] = d_ez[tid - 1]; 
     hy_temp[0] = d_hy[tid - 1]; 
    } 

    __syncthreads(); 

    /* update electric field */ 
    ez_temp[threadIdx.x] = ez_temp[threadIdx.x + 1] + (hy_temp[threadIdx.x + 1] - hy_temp[threadIdx.x]) * imp0; 

    /* use additive source at node 50 */ 
    if (tid == source) ez_temp[threadIdx.x] += exp(-(qTime - 30.) * (qTime - 30.)/100.); 

    if ((tid < N)&&(tid > 0)) d_ez[tid] = ez_temp[threadIdx.x]; 

} 

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

    // --- Problem size 
    const int SIZE = 10000000; 

    // --- Free-space wave impedance 
    double imp0 = 377.0; 

    // --- Maximum number of iterations (must be less than the problem size) 
    int maxTime = 100; 

    // --- Source location 
    int source = SIZE/2; 

    // --- Host side memory allocations and initializations 
    double *h_ez = (double*)calloc(SIZE, sizeof(double)); 
    double *h_hy = (double*)calloc(SIZE, sizeof(double)); 

    // --- Device side memory allocations and initializations 
    double *d_ez; gpuErrchk(cudaMalloc((void**)&d_ez, SIZE * sizeof(double))); 
    double *d_hy; gpuErrchk(cudaMalloc((void**)&d_hy, SIZE * sizeof(double))); 
    gpuErrchk(cudaMemset(d_ez, 0, SIZE * sizeof(double))); 
    gpuErrchk(cudaMemset(d_hy, 0, SIZE * sizeof(double))); 

    // --- Host side memory allocations for debugging purposes 
#ifdef DEBUG 
    double *h_ez_temp = (double*)calloc(SIZE, sizeof(double)); 
    double *h_hy_temp = (double*)calloc(SIZE, sizeof(double)); 
#endif 

    // --- Host-side time-steppings 
#ifndef DEBUG 
    TimingCPU timerCPU; 
    timerCPU.StartCounter(); 
    for (int qTime = 0; qTime < maxTime; qTime++) { 
     updateHost(h_ez, h_hy, imp0, qTime, source, SIZE); 
    } 
    printf("CPU elapsed time = %3.3f ms\n", timerCPU.GetCounter()); 
#endif 

    TimingGPU timerGPU; 
    timerGPU.StartCounter(); 
    // --- Device-side time-steppings 
    for (int qTime = 0; qTime < maxTime; qTime++) { 

     updateDevice_v0<<<iDivUp(SIZE, BLOCKSIZE), BLOCKSIZE>>>(d_ez, d_hy, imp0, qTime, source, SIZE); 
//  updateDevice_hy<<<iDivUp(SIZE, BLOCKSIZE), BLOCKSIZE>>>(d_ez, d_hy, imp0, qTime, source, SIZE); 
//  updateDevice_ez<<<iDivUp(SIZE, BLOCKSIZE), BLOCKSIZE>>>(d_ez, d_hy, imp0, qTime, source, SIZE); 
#ifdef DEBUG 
     gpuErrchk(cudaPeekAtLastError()); 
     gpuErrchk(cudaDeviceSynchronize()); 
     gpuErrchk(cudaMemcpy(h_ez_temp, d_ez, SIZE * sizeof(double), cudaMemcpyDeviceToHost)); 
     gpuErrchk(cudaMemcpy(h_hy_temp, d_hy, SIZE * sizeof(double), cudaMemcpyDeviceToHost)); 

     updateHost(h_ez, h_hy, imp0, qTime, source, SIZE); 
     for (int i=0; i<SIZE; i++) { 
      printf("%f %f %f %f\n",h_ez_temp[i], h_ez[i], h_hy_temp[i], h_hy[i]); 
     } 
     printf("\n"); 
#endif 
    } 
    printf("GPU elapsed time = %3.3f ms\n", timerGPU.GetCounter()); 

    return 0; 
} 

TimingCPU.h

#ifndef __TIMINGCPU_H__ 
#define __TIMINGCPU_H__ 

#ifdef __linux__ 

    class TimingCPU { 

     private: 
      long cur_time_; 

     public: 

      TimingCPU(); 

      ~TimingCPU(); 

      void StartCounter(); 

      double GetCounter(); 
    }; 

#elif _WIN32 || _WIN64 

    struct PrivateTimingCPU; 

    class TimingCPU 
    { 
     private: 
      PrivateTimingCPU *privateTimingCPU; 

     public: 

      TimingCPU(); 

      ~TimingCPU(); 

      void StartCounter(); 

      double GetCounter(); 

    }; // TimingCPU class 

#endif 

#endif 

TimingCPU.cpp

/**************/ 
/* TIMING CPU */ 
/**************/ 

#include "TimingCPU.h" 

#ifdef __linux__ 

    #include <sys/time.h> 
    #include <stdio.h> 

    TimingCPU::TimingCPU(): cur_time_(0) { StartCounter(); } 

    TimingCPU::~TimingCPU() { } 

    void TimingCPU::StartCounter() 
    { 
     struct timeval time; 
     if(gettimeofday(&time, 0)) return; 
     cur_time_ = 1000000 * time.tv_sec + time.tv_usec; 
    } 

    double TimingCPU::GetCounter() 
    { 
     struct timeval time; 
     if(gettimeofday(&time, 0)) return -1; 

     long cur_time = 1000000 * time.tv_sec + time.tv_usec; 
     double sec = (cur_time - cur_time_)/1000000.0; 
     if(sec < 0) sec += 86400; 
     cur_time_ = cur_time; 

     return 1000.*sec; 
    } 

#elif _WIN32 || _WIN64 
    #include <windows.h> 
    #include <iostream> 

    struct PrivateTimingCPU { 
     double PCFreq; 
     __int64 CounterStart; 
    }; 

    // --- Default constructor 
    TimingCPU::TimingCPU() { privateTimingCPU = new PrivateTimingCPU; (*privateTimingCPU).PCFreq = 0.0; (*privateTimingCPU).CounterStart = 0; } 

    // --- Default destructor 
    TimingCPU::~TimingCPU() { } 

    // --- Starts the timing 
    void TimingCPU::StartCounter() 
    { 
     LARGE_INTEGER li; 
     if(!QueryPerformanceFrequency(&li)) std::cout << "QueryPerformanceFrequency failed!\n"; 

     (*privateTimingCPU).PCFreq = double(li.QuadPart)/1000.0; 

     QueryPerformanceCounter(&li); 
     (*privateTimingCPU).CounterStart = li.QuadPart; 
    } 

    // --- Gets the timing counter in ms 
    double TimingCPU::GetCounter() 
    { 
     LARGE_INTEGER li; 
     QueryPerformanceCounter(&li); 
     return double(li.QuadPart-(*privateTimingCPU).CounterStart)/(*privateTimingCPU).PCFreq; 
    } 
#endif 

TimingGPU.cuh

#ifndef __TIMING_CUH__ 
#define __TIMING_CUH__ 

/**************/ 
/* TIMING GPU */ 
/**************/ 

// Events are a part of CUDA API and provide a system independent way to measure execution times on CUDA devices with approximately 0.5 
// microsecond precision. 

struct PrivateTimingGPU; 

class TimingGPU 
{ 
    private: 
     PrivateTimingGPU *privateTimingGPU; 

    public: 

     TimingGPU(); 

     ~TimingGPU(); 

     void StartCounter(); 
     void StartCounterFlags(); 

     float GetCounter(); 

}; // TimingCPU class 

#endif 

TimingGPU.cu

/**************/ 
/* TIMING GPU */ 
/**************/ 

#include "TimingGPU.cuh" 

#include <cuda.h> 
#include <cuda_runtime.h> 

struct PrivateTimingGPU { 
    cudaEvent_t  start; 
    cudaEvent_t  stop; 
}; 

// default constructor 
TimingGPU::TimingGPU() { privateTimingGPU = new PrivateTimingGPU; } 

// default destructor 
TimingGPU::~TimingGPU() { } 

void TimingGPU::StartCounter() 
{ 
    cudaEventCreate(&((*privateTimingGPU).start)); 
    cudaEventCreate(&((*privateTimingGPU).stop)); 
    cudaEventRecord((*privateTimingGPU).start,0); 
} 

void TimingGPU::StartCounterFlags() 
{ 
    int eventflags = cudaEventBlockingSync; 

    cudaEventCreateWithFlags(&((*privateTimingGPU).start),eventflags); 
    cudaEventCreateWithFlags(&((*privateTimingGPU).stop),eventflags); 
    cudaEventRecord((*privateTimingGPU).start,0); 
} 

// Gets the counter in ms 
float TimingGPU::GetCounter() 
{ 
    float time; 
    cudaEventRecord((*privateTimingGPU).stop, 0); 
    cudaEventSynchronize((*privateTimingGPU).stop); 
    cudaEventElapsedTime(&time,(*privateTimingGPU).start,(*privateTimingGPU).stop); 
    return time; 
}