(CUDA 编程5) CUDA编程接口(一)------一十八般武器
------GPU的革命
序言:所谓一十八般武器,不同的年代又有不同的说法,最早的汉武年间的:矛、镗、刀、戈、槊、鞭、锏、剑、锤、抓、戟、弓、钺、斧、牌、棍、枪、叉。到三国的:九长:刀、矛、戟、槊、镗、钺、棍、枪、叉;九短:斧、戈、牌、箭、鞭、剑、锏、锤、抓。再到明清的:弓、弩、枪、刀、剑、矛、盾、斧、钺、戟、黄、锏、挝、殳(棍)、叉、耙头、锦绳套索、白打(拳术)。《水浒传》里的:矛、锤、弓、弩、铳、鞭、锏、剑、链、挝、斧、钺、戈、戟、牌、棒、枪、扒。今天的武术届又有:刀、枪、剑、戟、斧、钺、钩、叉、鞭、锏、锤、抓、镗、棍、槊、棒、拐、流星。400多种古代冷兵器时代的武器,常用的也只有这么多种。也就像我们的API一样,API有无数多个,你自己都可以给自己造几个API出来,常用的,或者就那么多个。要打天下也不能扛着锄头,竹竿干吧。秦国之所以能统一六国,在武器上的统一,提供同一个的型号的武器装备(看秦的历史,就可以发现所有的兵器都是同一型号生产,弓弩上的器件可以互换,从兵马俑坑中找到的剑戟,箭头的尺寸误差很小,都可以互换),也是他能战胜其他六国的很好的基础。
Runtime API:cudaGetDeviceCount() 和 cudaGetDeviceProperties() 提供了遍历硬件设备,得到某个设备性能参数的功能。
int deviceCount;
cudaGetDeviceCount(&deviceCount);
int device;
for (device = 0; device < deviceCount; ++device) {
cudaDeviceProp deviceProp;
cudaGetDeviceProperties(&deviceProp, device);
}
cudaSetDevice() 设置某一块Device作为这个主机host上的某一个运行线程的设备:
cudaSetDevice(device);
这个函数必须要在使用 __global__ 的函数或者Runtime
的其他的API调用之前才能生效。 如果没有调用cudaSetDevice(),device 0 就会被设置为默认的设备,接下里的如果还有cudaSetDevice()函数也不会有效果。
Driver API:
cuDeviceGetCount()和cuDeviceGet()看名字就知道干嘛的~(英语不好的这应该能看明白吧- -!不要被我这个那 国家四级都没过的人BS你哈~!~)
int deviceCount;
cuDeviceGetCount(&deviceCount);
int device;
for (int device = 0; device < deviceCount; ++device) {
CUdevice cuDevice;
cuDeviceGet(&cuDevice, device);
int major, minor;
cuDeviceComputeCapability(&major, &minor, cuDevice);
}
使用 cudaMalloc() 或者 cudaMallocPitch() 来分配线性内存,通过cudaFree()释放内存.
下面是分配一个大小为256 float数组的方法:
float* devPtr;
cudaMalloc((void**)&devPtr, 256 * sizeof(float));
在使用2D数组的时候最好用cudaMallocPitch()来分配,在guide的第五章在讲到内存之间的调度的时候,就会看到他的好处。下面是一个分配一个大小为width×height 2D float数组的例子:
// host code
float* devPtr;
int pitch;
cudaMallocPitch((void**)&devPtr, &pitch,
width * sizeof(float), height);
myKernel<<<100, 512>>>(devPtr, pitch);
// device code
__global__ void myKernel(float* devPtr, int pitch)
{
for (int r = 0; r < height; ++r) {
float* row = (float*)((char*)devPtr + r * pitch);
for (int c = 0; c < width; ++c) {
float element = row[c];
}
}
}
CUDA 的数组方式,需要用 cudaMallocArray()和cudaFreeArray(). cudaMallocArray()又需要cudaCreateChannelDesc()来管理,这个其实可以在第guide的第五章里面可以看到,我们后面也会详细的介绍内存的调度和管理,和传统的GPU的内存方式不一样的地方.
分配 width×height 32位float的CUDA array例子:
cudaChannelFormatDesc channelDesc =
cudaCreateChannelDesc();
cudaArray* cuArray;
cudaMallocArray(&cuArray, &channelDesc, width, height);
cudaGetSymbolAddress用来在全局中定位一个数组的位置,然后 cudaGetSymbolSize()来回忆他分配的时候大小----如果有全局编程或者多线程编程经验的,或者用过几个函数同时处理一个数据的经验,都会为了把数据的独立性弄出来,不能让数据和函数耦合太大,一般都不会让函数直接牵扯上数据,只是在函数处理的时候重新定位数据----这地方有点绕~~
下面是一些例子,内存之间的拷贝,回想一下有几种内存~linear的有两个函数可以分配的,还有CUDA array的内存:
cudaMemcpy2DToArray(cuArray, 0, 0, devPtr, pitch,
width * sizeof(float), height,
cudaMemcpyDeviceToDevice);
The following code sample copies some host memory array to device memory:
float data[256];
int size = sizeof(data);
float* devPtr;
cudaMalloc((void**)&devPtr, size);
cudaMemcpy(devPtr, data, size, cudaMemcpyHostToDevice);
从host上面拷贝内存到device的constant上面:
__constant__ float constData[256];
float data[256];
cudaMemcpyToSymbol(constData, data, sizeof(data));
-#%#$^$^*^&系统int中断:(@$:突然发现已经是早上8点多了 - -!又一夜!@#¥%……!#……
Driver API:
cuMemAllocPitch()被推荐来作为2D的数组分配函数,会在内存对齐方面做一些check~然后保证在处理数据(像cuMemcpy2D()这样的拷贝函数)的时候达到最优的速度。下面就是一个float的width×height的2D的数组,并在循环里面处理的例子: ----(这个地方扩展提两个东东:一个是内存对齐,做程序优化的时候,或者处理网络问题的时候,都会遇到这些问题,内存对齐是一个普遍存在的问题,像SSE这样的编程的时候也是要求内存对齐的,看以后要是有机会以单独讲解内存对齐的问题:)第二个是CUDA的内存访问的问题,就是很多朋友都会在写kernel的时候,搞不明白里面的threadid,block id和传进来的内存的关系,这个地方必须要搞清楚的;内存和线程是CUDA编程必须搞明白的两个概念,不然到时候就会很混乱。其实看看前面的章节,应该能看明白的,不明白就用手画图~要是感觉还是有点不太清楚,也不要担心,接下来的章节会单独把一些难懂的问题更详细的讲解。)
// host code
CUdeviceptr devPtr;
int pitch;
cuMemAllocPitch(&devPtr, &pitch,
width * sizeof(float), height, 4);
cuModuleGetFunction(&cuFunction, cuModule, “myKernel”);
cuFuncSetBlockShape(cuFunction, 512, 1, 1);
cuParamSeti(cuFunction, 0, devPtr);
cuParamSetSize(cuFunction, sizeof(devPtr));
cuLaunchGrid(cuFunction, 100, 1);
// device code
__global__ void myKernel(float* devPtr)
{
for (int r = 0; r < height; ++r) {
float* row = (float*)((char*)devPtr + r * pitch);
for (int c = 0; c < width; ++c) {
float element = row[c];
}
}
}
cuArrayCreate()和cuArrayDestroy()来创建和释放CUDA array类型的数组。下面是一个width×height 32-bit float类型的CUDA array 分配的例子:
CUDA_ARRAY_DESCRIPTOR desc;
desc.Format = CU_AD_FORMAT_FLOAT;
desc.NumChannels = 1;
desc.Width = width;
desc.Height = height;
CUarray cuArray;
cuArrayCreate(&cuArray, &desc);
这个也是几个内存之间拷贝的例子:
CUDA_MEMCPY2D copyParam;
memset(©Param, 0, sizeof(copyParam));
copyParam.dstMemoryType = CU_MEMORYTYPE_ARRAY;
copyParam.dstArray = cuArray;
copyParam.srcMemoryType = CU_MEMORYTYPE_DEVICE;
copyParam.srcDevice = devPtr;
copyParam.srcPitch = pitch;
copyParam.WidthInBytes = width * sizeof(float);
copyParam.Height = height;
cuMemcpy2D(©Param);
拷贝host上面的内存到device上面:
float data[256];
int size = sizeof(data);
CUdeviceptr devPtr;
cuMemAlloc(&devPtr, size);
cuMemcpyHtoD(devPtr, data, size);
本来想一次把下面的4,5点也讲了~但是如果一下讲出来~lz帖子太长了~~这就不好了~~呵呵,实在的,看第四章的中文翻译的时候,就看了前面几个就不想看了~帖子不能老长老长的又不吸引人- -!hoho~ 所以后面的4,5就在下贴里面发了~~看了这么多也比较累的~好好的休闲一下~你还会发现出了4,5部分,还少了一个部分,那就是CUDA自己的函数,怎么定义,有device的,有global的,这个又有怎么区分啦~且听下回讲解。Hoho
ps:熬夜不好~熬夜很伤身……
有的时候,我们经常会用旧的东东来和新的东东比较,就像C和C++都不知道争论多少年了。其实很多时候我到觉得是没必要的争论,除非你是做C或者C++本身开发的。就像新的硬件不停的变化,以前的概念或者今天就不能用了,有的时候,我们关心架构在这个之上的程序开发就够了,没有太多的必要去问茴香豆的茴有几种写法。有的时候看到论坛里面的争论的时候,很多都不太清楚问题的也参加到争论里面,感觉就是明白事的没有几个,倒是起哄的不少 - -!人家说当局者迷,旁观者清,我看现在很多时候倒是当局者清,旁观者瞎起哄- -!呵呵,好像合乎了现在很多选秀节目的心理哈,起哄的人越多,人家的节目越红,哈哈~----扯远了……
Ps2:在学习API的时候,最好的方法就是多实验,多尝试API的性能,就像练习武术中的器械一样,经常用才会精通的,才能在这个基础上想出新的招式。就像有人在CSDN论坛问,怎么才能弄好ACM比赛,看到像刘汝佳写的《算法艺术与信息学竞赛》的书,都是一些方法,没有代码,就觉得很不解,就像问用什么代码来实现。我的回答就是:阅尽天下A×,心中自然无码。多做代码的练习,找一本数据结构的书,对照上面的代码都自己实现一遍,找一个代码练习的书,自己都重新写一遍。要不然就把像MSDN这样的介绍API的资料上的Demo能实现多少都去实现以下,呵呵。其实学习怎么编码都是一些基础工作。最重要的到最后都是算法的实现。其实到最后你会发现写程序就是数组的处理,就是字符串的处理……仅此而已~。所以不要小看了C语言那些书上的小程序例子,不要小看了输出(**)星星这样的例子,实际程序中很多时候都是处理这些星星~hoho~不要一上来就想去写什么游戏,写什么网络软件~先把字符串处理好了,就nb了,真的~你看ACM的题目,topcoder的题目,几乎都是字符串处理- -!哈哈。多实践,多去做一些demo例子。到真正用的时候就会觉得手到擒来,怎么用怎么顺手了。
还是那句话:约尽天下A×,心中自然无码。