ピン留め(Page-locked)されたHost-memoryの効果
CUDAによる一連の処理は、
cudaMemcpy
でデータ(input)をHostからDeviceにコピー- kernelを実行: inputを読んで処理してoutputに書く
cudaMemcpy
でデータ(output)をDeviceからHostにコピー
ってゆー一連のダンドリになります。
このとき Host-memoryをmalloc
/new
で確保するのとcudaHostAlloc
/cudaMallocHost
で確保するのとでは動きがかなり異なります。
#include <cuda_runtime.h> #include <device_launch_parameters.h> #include <math_functions.h> __global__ void kernel_sine(const float* angle, float* sine, unsigned int size) { int i = blockDim.x * blockIdx.x + threadIdx.x; if ( i < size ) { sine[i] = sinf(angle[i]); } } #include <cstdlib> #include <iostream> // どちらかひとつを有効にしておくれ #define MALLOC // #define CUDAMALLOCHOST int main() { const unsigned int N = 100000; // Host-memoryを確保 float* h_angle; float* h_sine; #if defined(MALLOC) h_angle = (float*)malloc(N*sizeof(float)); h_sine = (float*)malloc(N*sizeof(float)); #elif defined(CUDAMALLOCHOST) cudaMallocHost(&h_angle, N*sizeof(float)); cudaMallocHost(&h_sine , N*sizeof(float)); #endif // Device-memoryを確保 float* d_angle; float* d_sine; cudaMalloc(&d_angle, N*sizeof(float)); cudaMalloc(&d_sine , N*sizeof(float)); // HtoD, kernel, DtoH を二回実行 cudaMemcpyAsync(d_angle, h_angle, N*sizeof(float), cudaMemcpyDefault); kernel_sine<<<(N+255)/256,256>>>(d_angle, d_sine, N); cudaMemcpyAsync(h_sine , d_sine , N*sizeof(float), cudaMemcpyDefault); cudaMemcpyAsync(d_angle, h_angle, N*sizeof(float), cudaMemcpyDefault); kernel_sine<<<(N+255)/256,256>>>(d_angle, d_sine, N); cudaMemcpyAsync(h_sine , d_sine , N*sizeof(float), cudaMemcpyDefault); cudaStreamSynchronize(nullptr); // defaul-stream上の処理が完了するまで待機 std::cout << "done." << std::endl; // あとしまつ cudaFree(d_angle); cudaFree(d_sine ); #if defined(MALLOC) free(h_angle); free(h_sine ); #elif defined(CUDAMALLOCHOST) cudaFree(h_angle); cudaFree(h_sine ); #endif cudaDeviceReset(); }
実行時のtimelineはそれぞれこんな。
maloc
cudaMallocHost
malloc
だと一回目と二回目との間にスキマができてますが、cudaMallocHost
ではきっちり詰まってますよね。つまりそれだけ速いってことです。
PCI-bsを介してHost-Device間のコピーが行われるとき、DMA(Direct Memory Access)が使われます。CPUの動作とは無関係に(独立して)勝手にメモリをアクセスする機能です。DMAが機能するためにはコピー元/先の物理アドレスが固定されていなくてはなりません。
malloc
/new
で確保された領域は仮想アドレス空間にあり、物理アドレスが定まっていないのでDMAが使えない、そこで一旦 Staging-bufferと呼ばれる固定領域にコピーされた後、DMAが動きます。スキマが空くのはこのため。
一方cudaMallocHost
はハナっから固定された(Page-locked)領域を確保するのでいきなりDMAが動けるってスンポーです。
複数のStreamを使ったmemory-copyとkernelとのoverlapはHost-memoryが固定されていないとその効果が期待できません。
※ ただし、固定域をたくさん確保するとマシン全体のパフォーマンスを落とすことになりかねません。ご利用は計画的に。