CUDA GPU编程中使用结构体传递函数参数

CUDA GPU编程中使用结构体传递函数参数

 

         CUDA GPU编程中,虽然统一寻址(Unified Memory)技术能够大大简化编程难度和代码复杂度,但是速度略有牺牲,同时对运行环境提出更多的要求。而在不使用这项技术时,编程时需要同时创建CPU(host)和GPU(device)端的变量指针,然后为其分别分配内存。操作完成后,再分别释放内存。CUDA工程的范例程序中,单独编写了__global__函数,并将其和设备内存的创建,使用和释放包装在了一个函数里面,最终将这个包装后的函数提供给用户。用户只需管理自己的CPU内存,以及简单地调用该封装函数即可实现GPU加速。

但实际处理过程中,由于数据量大或者希望使用CUDA流来实现CPU和GPU并行,从而最大限度提高运行效率,由于内存的创建和释放效率极低,上诉方式并不可取。而是在程序开始创建变量指针并分配内存,随后对该内存进行多次使用,在程序运行结束时再释放资源。这样之前的一个封装函数显然不够用,现在需要用户自己创建各项主机和设备端内存指针,然后分别提供相应封装函数以实现内存的创建、核心的GPU并行计算代码、内存释放若干操作。应用程序往往需要使用若干数据,而分别要在主机和设备端建立一个指针,因此造成函数参数数量过多的问题,我的一些程序中经常超过20个参数,这对程序的调用和管理造成极大的不便。

         为了解决这个问题,考虑把相关必要的参数封装在一个结构体里面,通过该结构体的指针进行传输传递,而封装函数通过该结构体获取必要的数据,从而函数的参数大大降低。而进一步,把结构体内部指针变量对应的内存创建、释放封装起来,用户只需要创建相应的结构体再调用这些封装函数即可,对代码的易用性和易维护性均有很大的提升。

         下面首先提供一个范例,这是根据cuda工程创建的范例进行的修改。

#include "cuda_runtime.h"
#include "device_launch_parameters.h"

#include <stdio.h>
#include <string.h>

struct ProcPara
{
	int *h_a; // host CPU
	int *h_b; // host CPU
	int *h_c; // host CPU

	int *d_a; // device GPU
	int *d_b; // device GPU
	int *d_c; // device GPU

};

__global__ void addKernel(ProcPara* para)
{
	int i = threadIdx.x;
	para->d_c[i] = para->d_a[i] + para->d_b[i];
}

void InitProcPara(ProcPara* para, int arraySize)
{
cudaMallocHost((void**)&para->h_a, arraySize * sizeof(int));
cudaMallocHost((void**)&para->h_b, arraySize * sizeof(int));
cudaMallocHost((void**)&para->h_c, arraySize * sizeof(int));


cudaMalloc((void**)&para->d_a, arraySize * sizeof(int));
cudaMalloc((void**)&para->d_b, arraySize * sizeof(int));
cudaMalloc((void**)&para->d_c, arraySize * sizeof(int));

}

void DeinitProcPara(ProcPara* para)
{
	// free host memory
	cudaFreeHost(para->h_a);
	cudaFreeHost(para->h_b);
	cudaFreeHost(para->h_c);
	// free device memory
	cudaFree(para->d_a);
	cudaFree(para->d_b);
	cudaFree(para->d_c);
}

void addWithCuda(ProcPara* para, unsigned int arraySize)
{
	cudaSetDevice(0);
	cudaMemcpy(para->d_a, para->h_a, arraySize * sizeof(int), cudaMemcpyHostToDevice);
	cudaMemcpy(para->d_b, para->h_b, arraySize * sizeof(int), cudaMemcpyHostToDevice);

	// Launch a kernel on the GPU with one thread for each element.
	addKernel << <1, arraySize >> >(para);

	cudaMemcpy(para->h_c, para->d_c, arraySize * sizeof(int), cudaMemcpyDeviceToHost);
	cudaDeviceSynchronize();
}





int main()
{
	const int arraySize = 5;
	const int a[arraySize] = { 1, 2, 3, 4, 5 };
	const int b[arraySize] = { 10, 20, 30, 40, 50 };
	int c[arraySize] = { 0 };

	ProcPara* para = new ProcPara;

	InitProcPara(para, arraySize);


	memcpy(para->h_a, a, arraySize * sizeof(int));
	memcpy(para->h_b, b, arraySize * sizeof(int));

	addWithCuda(para, arraySize);

	memcpy(c, para->h_c, arraySize * sizeof(int));

	printf("{1,2,3,4,5} + {10,20,30,40,50} = {%d,%d,%d,%d,%d}\n",
		c[0], c[1], c[2], c[3], c[4]);

	DeinitProcPara(para);
	delete para;

	return 0;
}



但是,这个程序并不能得到正确的结果。

这是因为,在传递给__global__函数的结构体指针的内存实体在主机内存中。而在非Unified Memory的条件下,GPU是不能访问到CPU端的内存数据的。由此,要继续使用结构体传递参数,需要进行如下的操作:

分别创建主机端和设备端结构体指针,并分配相应的内存。为主机端结构体内部成员变量分别分配主机端和设备端内存。然后将主机端结构体数据复制给设备端结构体。在核心计算代码中,主机端程序使用主机端结构体指针来获取所有数据的指针(包括数据拷贝),设备端程序使用设备端结构体指针获取设备端数据的指针。修改后的代码如下,这里需要注意的是,分配内存用到了指向指针的指针。因为指针本身是个变量,其保存的是个地址,分配内存要对该变量的内容进行更新,必须传递该变量的指针才有效,否则只是改变了参数变量的内容。这也是为什么cudaMalloc使用的是指针的指针的原因。

 

#include "cuda_runtime.h"
#include "device_launch_parameters.h"

#include <stdio.h>
#include <string.h>

struct ProcPara
{
	int *h_a; // host CPU
	int *h_b; // host CPU
	int *h_c; // host CPU

	int *d_a; // device GPU
	int *d_b; // device GPU
	int *d_c; // device GPU

};

__global__ void addKernel(ProcPara* d_para)
{
	int i = threadIdx.x;
	d_para->d_c[i] = d_para->d_a[i] + d_para->d_b[i];

}

void InitProcPara(ProcPara**ha_para, ProcPara**da_para, int arraySize)
{
	//allocate stucture memory
	cudaMallocHost((void**)ha_para, sizeof(ProcPara));
	cudaMalloc((void**)da_para, sizeof(ProcPara));


	ProcPara*h_para = *ha_para;

	cudaMallocHost((void**)&h_para->h_a, arraySize * sizeof(int));
	cudaMallocHost((void**)&h_para->h_b, arraySize * sizeof(int));
	cudaMallocHost((void**)&h_para->h_c, arraySize * sizeof(int));

	cudaMalloc((void**)&h_para->d_a, arraySize * sizeof(int));
	cudaMalloc((void**)&h_para->d_b, arraySize * sizeof(int));
	cudaMalloc((void**)&h_para->d_c, arraySize * sizeof(int));

	// exchange data
	cudaMemcpy(*da_para, *ha_para, sizeof(ProcPara), cudaMemcpyHostToDevice);

}

void DeinitProcPara(ProcPara*h_para, ProcPara*d_para)
{
	// free host memory
	cudaFreeHost(h_para->h_a);
	cudaFreeHost(h_para->h_b);
	cudaFreeHost(h_para->h_c);
	// free device memory
	cudaFree(h_para->d_a);
	cudaFree(h_para->d_b);
	cudaFree(h_para->d_c);

	//release stucture memory
	cudaFreeHost(h_para);
	cudaFree(d_para);

}

void addWithCuda(ProcPara* h_para, ProcPara* d_para, unsigned int arraySize)
{
	cudaSetDevice(0);

	cudaMemcpy(h_para->d_a, h_para->h_a, arraySize * sizeof(int), cudaMemcpyHostToDevice);
	cudaMemcpy(h_para->d_b, h_para->h_b, arraySize * sizeof(int), cudaMemcpyHostToDevice);

	cudaDeviceSynchronize();

	// Launch a kernel on the GPU with one thread for each element.
	addKernel << <1, 5 >> >(d_para);

	cudaMemcpy(h_para->h_c, h_para->d_c, arraySize * sizeof(int), cudaMemcpyDeviceToHost);
	cudaDeviceSynchronize();

}



int main()
{
	const int arraySize = 5;
	const int a[arraySize] = { 1, 2, 3, 4, 5 };
	const int b[arraySize] = { 10, 20, 30, 40, 50 };
	int c[arraySize] = { 0 };

	ProcPara *h_para;
	ProcPara *d_para;


	InitProcPara(&h_para, &d_para, arraySize);


	memcpy(h_para->h_a, a, arraySize * sizeof(int));
	memcpy(h_para->h_b, b, arraySize * sizeof(int));

	addWithCuda(h_para, d_para, arraySize);

	memcpy(c, h_para->h_c, arraySize * sizeof(int));

	printf("{1,2,3,4,5} + {10,20,30,40,50} = {%d,%d,%d,%d,%d}\n",c[0], c[1], c[2], c[3], c[4]);

	DeinitProcPara(h_para, d_para);

	return 0;
}



猜你喜欢

转载自blog.csdn.net/u013701860/article/details/52605137