前面基礎部分說cpu內存跟gpu內存在各自的函數中不能相互操作。申請主機內存用malloc,釋放用free。但其實cuda可以通過DMA(Direct Memory Access)把主機內存地址直接映射到cuda地址,稱爲鎖頁內存(pinned memory or page locked memory)
鎖頁內存
主機端存在虛擬內存,主機內存不足是會將內存數據交換到虛擬內存中,虛擬內存就是主機中的磁盤空間,需要該頁時再重新從磁盤加載回來。這樣做可以使用比實際內存更大的內存空間。
鎖頁內存允許GPU上的MDA控制器在使用主機內存時不用CPU參與。GPU上的顯存都是鎖頁的,因爲GPU上的內存時不支持交換到磁盤的。鎖頁內存就是分配主機內存時鎖定該頁,讓其不與磁盤交換。
CUDA中鎖頁內存的使用可以使用CUDA驅動API( driver API’s)cuMemAllocHost()或者使用CUDA的運行時API(runtime API)中的cudaMallocHost(),同時釋放要用cudaFreeHost()。除此之外還可以直接用主機上Malloc()分配的空間,然後將其註冊爲鎖頁內存(使用cudaHostRegister()函數完成註冊)。
使用鎖頁內存的好處有以下幾點:
- 設備內存與鎖頁內存之間的數據傳輸可以與內核執行並行處理。
- 鎖頁內存可以映射到設備內存,減少設備與主機的數據傳輸。
- 在前端總線的主機系統鎖頁內存與設備內存之間的數據交換會比較快;並且可以是write-combining的,此時帶寬會跟大。
在多線程的不同線程裏調用kernel函數,就是cuda多線程。 如果要所有的線程都可以使用鎖頁內存的好處,需要在分配時將cudaHostAllocPortable標誌傳給cudaMallocHost(),或者將cudaHostRegisterPortable標誌傳給函數cudaHostRegister()
默認情況下鎖頁內存時可緩存的,可以再使用cudaMallocHost()函數時使用cudaHostAllocWriteCombined標誌聲明爲write-combining的,write-combining內存沒有一二級緩存,這樣其他的應用可擁有更多的緩存資源。此外write-combining在PCI總線的系統中沒有snooped過程,可以獲得高達40%的傳輸加速。但是從主機讀取write-combining內存速度很慢,因此應該用於主機端只寫的數據。
要將鎖頁內存映射到設備內存的地址空間還需要在cudaMalloHost()中使用cudaHostAllocMapped標誌,或者在用cudaHostRegister()函數註冊時使用標誌cudaHostRegisterMapped。
用來分配一塊被映射到設備內存空間的鎖頁內存。這樣的鎖頁內存會有兩個內存地址:主機上的內存地址和設備上的內存地址。主機內存地址直接由函數cudaMallocHost()或Malloc()返回,設備內存地址則由函數cudaHostGetDevicePointer()查詢,用以在kernel中訪問鎖頁內存。
多線程、頁鎖存的點乘示例
#include "../common/book.h"
#define imin(a,b) (a<b?a:b)
#define N (33*1024*1024)
const int threadsPerBlock = 256;
const int blocksPerGrid =
imin( 32, (N/2+threadsPerBlock-1) / threadsPerBlock );
__global__ void dot( int size, float *a, float *b, float *c ) {
__shared__ float cache[threadsPerBlock];
int tid = threadIdx.x + blockIdx.x * blockDim.x;
int cacheIndex = threadIdx.x;
float temp = 0;
while (tid < size) {
temp += a[tid] * b[tid];
tid += blockDim.x * gridDim.x;
}
// set the cache values
cache[cacheIndex] = temp;
// synchronize threads in this block
__syncthreads();
// for reductions, threadsPerBlock must be a power of 2
// because of the following code
int i = blockDim.x/2;
while (i != 0) {
if (cacheIndex < i)
cache[cacheIndex] += cache[cacheIndex + i];
__syncthreads();
i /= 2;
}
if (cacheIndex == 0)
c[blockIdx.x] = cache[0];
}
struct DataStruct {
int deviceID;
int size;
int offset;
float *a;
float *b;
float returnValue;
};
void* routine( void *pvoidData ) {
DataStruct *data = (DataStruct*)pvoidData;
if (data->deviceID != 0) {
HANDLE_ERROR( cudaSetDevice( data->deviceID ) );
HANDLE_ERROR( cudaSetDeviceFlags( cudaDeviceMapHost ) );
}
int size = data->size;
float *a, *b, c, *partial_c;
float *dev_a, *dev_b, *dev_partial_c;
// allocate memory on the CPU side
a = data->a;
b = data->b;
partial_c = (float*)malloc( blocksPerGrid*sizeof(float) );
// allocate the memory on the GPU
HANDLE_ERROR( cudaHostGetDevicePointer( &dev_a, a, 0 ) );
HANDLE_ERROR( cudaHostGetDevicePointer( &dev_b, b, 0 ) );
HANDLE_ERROR( cudaMalloc( (void**)&dev_partial_c,
blocksPerGrid*sizeof(float) ) );
// offset 'a' and 'b' to where this GPU is gets it data
dev_a += data->offset;
dev_b += data->offset;
dot<<<blocksPerGrid,threadsPerBlock>>>( size, dev_a, dev_b,
dev_partial_c );
// copy the array 'c' back from the GPU to the CPU
HANDLE_ERROR( cudaMemcpy( partial_c, dev_partial_c,
blocksPerGrid*sizeof(float),
cudaMemcpyDeviceToHost ) );
// finish up on the CPU side
c = 0;
for (int i=0; i<blocksPerGrid; i++) {
c += partial_c[i];
}
HANDLE_ERROR( cudaFree( dev_partial_c ) );
// free memory on the CPU side
free( partial_c );
data->returnValue = c;
return 0;
}
int main( void ) {
int deviceCount;
HANDLE_ERROR( cudaGetDeviceCount( &deviceCount ) );
if (deviceCount < 2) {
printf( "We need at least two compute 1.0 or greater "
"devices, but only found %d\n", deviceCount );
return 0;
}
cudaDeviceProp prop;
for (int i=0; i<2; i++) {
HANDLE_ERROR( cudaGetDeviceProperties( &prop, i ) );
if (prop.canMapHostMemory != 1) {
printf( "Device %d can not map memory.\n", i );
return 0;
}
}
float *a, *b;
HANDLE_ERROR( cudaSetDevice( 0 ) );
HANDLE_ERROR( cudaSetDeviceFlags( cudaDeviceMapHost ) );
HANDLE_ERROR( cudaHostAlloc( (void**)&a, N*sizeof(float),
cudaHostAllocWriteCombined |
cudaHostAllocPortable |
cudaHostAllocMapped ) );
HANDLE_ERROR( cudaHostAlloc( (void**)&b, N*sizeof(float),
cudaHostAllocWriteCombined |
cudaHostAllocPortable |
cudaHostAllocMapped ) );
// fill in the host memory with data
for (int i=0; i<N; i++) {
a[i] = i;
b[i] = i*2;
}
// prepare for multithread
DataStruct data[2];
data[0].deviceID = 0;
data[0].offset = 0;
data[0].size = N/2;
data[0].a = a;
data[0].b = b;
data[1].deviceID = 1;
data[1].offset = N/2;
data[1].size = N/2;
data[1].a = a;
data[1].b = b;
CUTThread thread = start_thread( routine, &(data[1]) );
routine( &(data[0]) );
end_thread( thread );
// free memory on the CPU side
HANDLE_ERROR( cudaFreeHost( a ) );
HANDLE_ERROR( cudaFreeHost( b ) );
printf( "Value calculated: %f\n",
data[0].returnValue + data[1].returnValue );
return 0;
}