奋斗了2周,终于把CUDA的内存与显存数据拷贝、pitch、以及如何对显存的数组进行引用弄明白了。很开心。
我是初次接触CUDA,学习CUDA是因为我了解到它的并行性使得数据量很大的程序的运行效率很高。我现在做的项目恰好需要这种高性能的并行运算。
我开始编写了几个内存和显存之间普通的int、char、float和double型数据的拷贝进行测试,然后进行了一维数组在内存和显存之间数据的拷贝,随后就是二维数组了。
在一维数组的定义和引用中,我开始是定义的时候指定数组的大小,后来改写为动态申请内存和显存。如下:
定义一个一维数组:
float *cpu_data; //内存中的一维数组指针
int width; //数组的宽度
cpu_data = (float*)malloc(sizeof(float) * width);
float *gpu_data; //显存中的一维数组指针
cutilSafeCall( cudaMalloc((void**) &gpu_data, sizeof(float) * width)); //为数组申请显存空间
for(int c = 0; c < width; ++c) //内存数组的初始化
cpu_data[c] = c;
//下面进行数据的拷贝 将内存数据拷贝到显存对应数组中
cutilSafeCall( cudaMemcpy( gpu_data, cpu_data, sizeof(float) * width, cudaMemcpyHostToDevice));
对于二维数组,在显存上申请空间是最好使用cudaMallocPitch()函数,在此函数中有一个参数pitch,这个参数是补齐时显存数组每一行占得字节数。注意它是一个传出参数,其类型是size_t,也就是unsigned int类型。对于二维数组在内存和显存之间的拷贝最好使用函数
cudaMemcpy2D(),这样效率高些.
float *cpu_data; //CPU上的数组
float *gpu_data; //GPU上的数组
int width, height; //数组的宽度(列数)和高度(行数)
int pitch; //GPU上数组的pitch
printf("Input the width and height/n"); //输入数组的列数和行数
scanf("%d%d", &width, &height);
cpu_data = (float*)malloc(sizeof(float)*width*height); //申请内存空间
cutilSafeCall( cudaMallocPitch( (void**) &gpu_data, &pitch, sizeof(float) * width, height)); //申请显存空间
cutilSafeCall( cudaMemcpy2D( gpu_data, pitch, cpu_data, sizeof(float) * width, sizeof(float) * width, height, cudaMemcpyHostToDevice)); //内存和显存数据进行拷贝
下面是我写的最终的程序:
/**************************************************************************
*矩阵相加的例子
***************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <cutil_inline.h>
/************************************************************************
* Init CUDA
************************************************************************/
#if __DEVICE_EMULATION__
bool InitCUDA(void){return true;}
#else
bool InitCUDA(void)
{
int count = 0;
int i = 0;
cudaGetDeviceCount(&count);
if(count == 0) {
fprintf(stderr, "There is no device./n");
return false;
}
for(i = 0; i < count; i++) {
cudaDeviceProp prop;
if(cudaGetDeviceProperties(&prop, i) == cudaSuccess) {
if(prop.major >= 1) {
break;
}
}
}
if(i == count) {
fprintf(stderr, "There is no device supporting CUDA./n");
return false;
}
cudaSetDevice(i);
printf("CUDA initialized./n");
return true;
}
#endif
/*****************************************************************************************************
*kernel函数,矩阵相加
******************************************************************************************************/
__global__ void myKernel(const float *a, const float *b, float *c, size_t pitch, int height, int width)
{
int i = blockIdx.y * blockDim.y + threadIdx.y;
int j = blockIdx.x * blockDim.x + threadIdx.x;
if(i < height && j < width)
c[i * pitch/ sizeof(float) + j] = a[i * pitch / sizeof(float) + j] + b[i * pitch / sizeof(float) + j];
}
int main(int argc, char* argv[])
{
if(!InitCUDA())
return 0;
//CPU上的3个矩阵数组
float *cpu_A;
float *cpu_B;
float *cpu_C;
//GPU上的3个矩阵数组
float *gpu_A;
float *gpu_B;
float *gpu_C;
int width = 3; //矩阵的宽度(列数)
int height = 2;//矩阵的高度(行数)
size_t pitch; //GPU数组的pitch
//为CPU上的矩阵数组申请内存空间
cpu_A = (float*)malloc(sizeof(float) * width * height);
cpu_B = (float*)malloc(sizeof(float) * width * height);
cpu_C = (float*)malloc(sizeof(float) * width * height);
//为GPU上的矩阵数组申请显存空间
cutilSafeCall( cudaMallocPitch((void**) &gpu_A, &pitch, sizeof(float) * width, height));
cutilSafeCall( cudaMallocPitch((void**) &gpu_B, &pitch, sizeof(float) * width, height));
cutilSafeCall( cudaMallocPitch((void**) &gpu_C, &pitch, sizeof(float) * width, height));
//将pitch打印
printf("The pitch is: %d/n", pitch);
//为CPU上的矩阵数组初始化
for(int r = 0; r < height; ++r){
for(int c = 0; c < width; ++c){
cpu_A[r * width + c] = r * c;
cpu_B[r * width + c] = r + c;
cpu_C[r * width + c] = 0.0;
}
}
//打印CPU上的矩阵数组
printf("/nCPU_A DATA/n");
for(int r = 0; r < height; ++r){
for(int c = 0; c < width; ++c){
printf("%f/t", cpu_A[r * width + c]);
}
printf("/n");
}
printf("/nCPU_B DATA/n");
for(int r = 0; r < height; ++r){
for(int c = 0; c < width; ++c){
printf("%f/t", cpu_B[r * width + c]);
}
printf("/n");
}
printf("/nCPU_C DATA/n");
for(int r = 0; r < height; ++r){
for(int c = 0; c < width; ++c){
printf("%f/t", cpu_C[r * width + c]);
}
printf("/n");
}
//将CPU上的矩阵数组cpu_A、cpu_B分别拷贝到GPU上的矩阵数组gpu_A、gpu_B中
cutilSafeCall( cudaMemcpy2D( gpu_A, pitch, cpu_A, sizeof(float) * width, sizeof(float) * width, height, cudaMemcpyHostToDevice));
cutilSafeCall( cudaMemcpy2D( gpu_B, pitch, cpu_B, sizeof(float) * width, sizeof(float) * width, height, cudaMemcpyHostToDevice));
dim3 Dg(1, 2, 1); //定义整个grid的维度和尺寸
dim3 Db(width, 1, 1); //定义每个block的维度和尺寸
myKernel<<<Dg, Db, 0>>>(gpu_A, gpu_B, gpu_C, pitch, height, width); //调用kernel函数
//将显存数组gpu_C拷贝会内存数组cpu_C
cutilSafeCall( cudaMemcpy2D( cpu_C, sizeof(float) * width, gpu_C, pitch, sizeof(float) * width, height, cudaMemcpyDeviceToHost));
//打印CPU_C数组
printf("/nAfter change CPU_C DATA/n");
for(int r = 0; r < height; ++r){
for(int c = 0; c < width; ++c){
printf("%f/t", cpu_C[r * width + c]);
}
printf("/n");
}
//释放内存空间
free(cpu_A);
free(cpu_B);
free(cpu_C);
//释放显存空间
cutilSafeCall( cudaFree(gpu_A));
cutilSafeCall( cudaFree(gpu_B));
cutilSafeCall( cudaFree(gpu_C));
//退出CUDA
CUT_EXIT(argc, argv);
return 0;
}
2周的编程(当然是闲暇时间,呵呵),我终于开始了我的CUDA编程之路。
路漫漫其修远兮,吾将上下而求索。