CUDA内存操作函数
刚刚接触cuda
编程,涉及Nvidia
的GPU
编程模式,主要第一步要考虑GPU
与CPU
的内存交互与操作。下面简单介绍一下主要的几个CPU
与GPU
函数分析:
介绍一个常用称呼,CPU
上面的一般称作Host
,GPU
称作Device
设备的名词。下面我们分别看一下Host
与Device
拷贝函数的源码:
malloc()与cudaMalloc()函数
cudaMalloc()
函数负责向设备device
申请分配一定字节的线性内存,并以devPtr
的指针返回该内存首地址。使用法则几乎与C
语言中的malloc()
函数一样,只是在GPU
里面分配内存。
/*
_cudaError_t cudaMalloc ( void** devPtr, size_t size );
Allocate memory on the device.
Parameters
devPtr
- Pointer to allocated device memory
size
- Requested allocation size in bytes
*/
使用方法:当然下面的cudaMalloc()
会返回是否分配内存成功标志,通过cudaError_t
看是否返回cudaSuccess
的标志。
memcpy()与cudaMemcpy()、cudaMemcpyAsync()函数
cudaMemcpy()
函数负责主机和设备之间的数据传输,就是将主机数据拷贝至设备,或者设备数据拷贝至主机。其中该函数会以同步方式执行,即在数据拷贝操作未完成时候应用程序是阻塞的。
/*
cudaError_t cudaMemcpy ( void* dst, const void* src, size_t count, cudaMemcpyKind kind )
Copies data between host and device.
Parameters
dst
- Destination memory address // 目的存储指针
src
- Source memory address // 数据源指针
count
- Size in bytes to copy // 数据大小
kind
- Type of transfer // 设备拷贝数据
*/
host
与device
的内存拷贝主要是从host
拷贝到device
或者是device
拷贝到host
上面,这个参数是在cudaMemcpy()
中的kind
来进行确定。
// kind:
cudaMemcpyHostToDevice // 从host拷贝至host
cudaMemcpyHostToDevice // 从host拷贝至device
cudaMemcpyDeviceToHost // 从device拷贝至host
cudaMemcpyDeviceToDevice // 从host拷贝至device
cudaMemcpyAsync()
与cudaMemcpy()
区别在于它是异步的,而cudaMemcpy()
是同步的,异步的操作最常见的就是在device
上面进行拷贝操作。cudaMemcpyDeviceToDevice
就是kind
最常设置的参数变量。
/*
__host____device__cudaError_t cudaMemcpyAsync ( void* dst, const void* src, size_t count, cudaMemcpyKind kind, cudaStream_t stream = 0 )
Copies data between host and device.
Parameters
dst
- Destination memory address // 目的指针
src
- Source memory address // 数据源指针
count
- Size in bytes to copy // 拷贝大小
kind
- Type of transfer // 设备迁移
stream
- Stream identifier // 默认streams还是指定streams
*/
memset()与cudaMemset()函数
谈及memset()
函数与cudaMemset()
时候,记住初始化的时候千万别直接使用memset()
或者cudaMemset()
进行初始化,除非你初始化的内存里面每个元素为0。不然,建议你先在CPU
里面进行初始化,然后将数据考入cuda
内存里面。
/*
_cudaError_t cudaMemset ( void* devPtr, int value, size_t count )
Initializes or sets device memory to a value.
Parameters
devPtr
- Pointer to device memory
value
- Value to set for each byte of specified memory
count
- Size in bytes to set
*/
标准的C函数 | CUDA C函数 |
---|---|
malloc | cudaMalloc |
memcpy | cudaMemcpy |
memset | cudaMemset |
free | cudaFree |
下面例举一个最简单的小例子:两个内存数据进行相加操作,分别通过在CPU
与GPU
上操作来简单实用一下关于CUDA
的相关内存分配、内存拷贝、内存释放的函数。以此来加深使用的印象。
#include <cuda_runtime.h>
#include <stdio.h>
// 判断cuda相关函数是否执行成功
#define CHECK(call) \
{ \
const cudaError_t error = call; \
if (error != cudaSuccess) \
{ \
fprintf(stderr, "Error: %s:%d, ", __FILE__, __LINE__); \
fprintf(stderr, "code: %d, reason: %s\n", error, \
cudaGetErrorString(error)); \
exit(1); \
} \
}
// 检测cpu与gpu的数据是否完全一致
void checkResult(float *hostRef, float *gpuRef, const int N)
{
double epsilon = 1.0E-8;
bool match = 1;
for (int i = 0; i < N; i++)
{
if (abs(hostRef[i] - gpuRef[i]) > epsilon)
{
match = 0;
printf("Arrays do not match!\n");
printf("host %5.2f gpu %5.2f at current %d\n", hostRef[i],
gpuRef[i], i);
break;
}
}
if (match)
printf("Arrays match.\n\n");
return;
}
// 随机生成数据
void initialData(float *ip, int size)
{
// generate different seed for random number
time_t t;
srand((unsigned) time(&t));
for (int i = 0; i < size; i++)
{
ip[i] = (float)(rand() & 0xFF) / 10.0f;
}
return;
}
// 主机上cpu进行相加操作
void sumArraysOnHost(float *A, float *B, float *C, const int N)
{
for (int idx = 0; idx < N; idx++)
C[idx] = A[idx] + B[idx];
}
// 设备上通过gpu核函数进行操作
__global__ void sumArraysOnGPU(float *A, float *B, float *C, const int N)
{
int i = threadIdx.x;
if (i < N)
C[i] = A[i] + B[i];
}
int main(int argc, char **argv)
{
printf("%s Starting...\n", argv[0]);
// 判断设备是否启动
int dev = 0;
CHECK(cudaSetDevice(dev));
// 设置元素的内存大小
int nElem = 1 << 5;
printf("Vector size %d\n", nElem);
// 分配主机内存
size_t nBytes = nElem * sizeof(float);
float *h_A, *h_B, *hostRef, *gpuRef;
h_A = (float *)malloc(nBytes);
h_B = (float *)malloc(nBytes);
hostRef = (float *)malloc(nBytes);
gpuRef = (float *)malloc(nBytes);
// 初始化数据至主机分配的内存中
initialData(h_A, nElem);
initialData(h_B, nElem);
// 初始化为0
memset(hostRef, 0, nBytes);
memset(gpuRef, 0, nBytes);
// 设备分配全局内存
float *d_A, *d_B, *d_C;
CHECK(cudaMalloc((float**)&d_A, nBytes));
CHECK(cudaMalloc((float**)&d_B, nBytes));
CHECK(cudaMalloc((float**)&d_C, nBytes));
// 主机数据拷贝至设备上
CHECK(cudaMemcpy(d_A, h_A, nBytes, cudaMemcpyHostToDevice));
CHECK(cudaMemcpy(d_B, h_B, nBytes, cudaMemcpyHostToDevice));
CHECK(cudaMemcpy(d_C, gpuRef, nBytes, cudaMemcpyHostToDevice));
// 核函数的参数block与grid设置
dim3 block (nElem);
dim3 grid (1);
// 核函数调用
sumArraysOnGPU<<<grid, block>>>(d_A, d_B, d_C, nElem);
printf("Execution configure <<<%d, %d>>>\n", grid.x, block.x);
// 将数据拷贝至主机
CHECK(cudaMemcpy(gpuRef, d_C, nBytes, cudaMemcpyDeviceToHost));
// 主机上的数据进行相加操作
sumArraysOnHost(h_A, h_B, hostRef, nElem);
// 对比设备上的数据与主机上操作的结过差异
checkResult(hostRef, gpuRef, nElem);
// 释放设备开辟的内存
CHECK(cudaFree(d_A));
CHECK(cudaFree(d_B));
CHECK(cudaFree(d_C));
// 释放主机上开辟的内存
free(h_A);
free(h_B);
free(hostRef);
free(gpuRef);
CHECK(cudaDeviceReset());
return(0);
}
小结
上述的CUDA
相关内存操作函数能够有效的将数据从CPU
与GPU
之间相互传递,同时这些CUDA
的内存操作函数是拥有隐式阻塞功能,即内存拷贝结束才可以继续往下执行的功能。上述为最常见的CUDA
关于内存操作的函数,当然还会存在一些CUDA
特殊操作的函数,后续再进行研究学习。