위의 의견에서 가장 중요한 질문과 토론은 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;
}
여기서 'n'은 정의 되었습니까? 그 가치는 어느 것입니까? CPU 버전을 어떻게 지내셨습니까? GPU 시간 내에 'Initial condition'과 'Enforcing special formula'도 고려하고 있음에 유의하십시오. – pQB
또한 while 루프 내에 호스트 배열 um과 u를 업데이트하는 루프가 있으며 나중에 컨텐츠를 cudaMemcpy 호출로 대체합니다. 이 루프를 제거하고 다시 측정하십시오. – brano
T, L, n, c의 값은 얼마입니까? 또한 스트림을 만들지 만 결코 사용하지 않으며 결코 파괴하지 않습니다. 스트림 생성을 제거합니다. – brano