步子跨得太大,内存分配的事情有点糊涂,梳理一下。
我们直到CUDA运算时候基本分为:
- host数据初始化
- device分配内存
- 把host数据赋值到device上
- 进行CUDA kernel运算
- 把CUDA运算结果写回到host上
基本上就这几步,但是这中间的内存操作其实还蛮多细节。
我们拿一个简单的程序举例:
#include<stdio.h>
__global__ void add(const double *x,const double *y,const double *z,int size){
}
int main(){
int N = 1000;
int M = N*sizeof(double);
double *h_x = (double*)malloc(N);
double *h_y = (double*)malloc(N);
double *h_z = (double*)malloc(N);
for(int i = 0;i < N;i++){
h_x[i] = rand() % 2;
h_y[i] = rand() % 2;
}
这是第一部分,可以看到我们已经把host的数据初始化了。接下来的操作我们需要把这些数据转移到CUDA上。转移到CUDA上得需要变量呢,所以继续初始化
double *d_x,*d_y,*d_z;
cudaMalloc((void**)&d_x,M);
cudaMalloc((void**)&d_y,M);
cudaMalloc((void**)&d_z,M);
到这里为止,CPU上的d_x,d_y,d_z的值是GPU上的三个大小为M的内存地址。其中cudaMalloc的函数说明如下:
cudaError_t cudaMalloc(void **address,size_t size);
可以看到第一个参数就是指针的指针,也就是指针的地址。所以我们这里给d_x,d_yd_z在加一个&来表示指针的地址,其中前面的void**表示强制类型转换。
所以cudaMalloc(void **address,size_t size)的作用就是在Device上开辟一段大小为size的内存空间,然后将这个内存空间的地址赋值给address。清楚了这些后,就知道我们上面在device上开辟的是M大小的内存空间。接下来我们需要把这个GPU中M大小的内存空间的值填满元素,然后计算得到结果呀:
把h_x所指向的值复制到d_x所指的内存空间(GPU上的内存空间)
cudaMemcpy(d_x,h_x,M,cudaMemcpyHostToDevice);
cudaMemcpy(d_y,h_y,M,cudaMemcpyHostToDevice);
add<<<1,N>>>(d_x,d_y,d_z,N);
这个也显而易见,cudaMemcpy是把h_x所指的内存空间值直接赋值给d_x,d_y所值的内存空间值,这样的话CPU的数据就迁移到GPU数据上了。然后我们调用CUDA kernel核进行计算add<<<>>>,计算结果存放到d_z所指的内存空间中。我们还需要把d_z所指内容复制到h_z完成CUDA的一套流程:‘
cudaMemcpy(h_z,d_z,M,cudaMemcpyDeviceToHost);
最后记得free掉我们之前malloc的内存
free(h_x);
free(h_y);
free(h_z);
cudaFree(d_x);
cudaFree(d_y);
cudaFree(d_z);
return 0;