OpenHero 开勇

Open heart, bravely fly!

赵开勇ID:OpenHero
210270次访问,排名299好友95人,关注者104
OpenHero的文章
原创 234 篇
翻译 2 篇
转载 51 篇
评论 386 篇
OpenHero 开勇的公告
最近评论
OpenHero:就是在计算thread在全局的位置的时候:unsigned int index = xIndex(6) + size_x * yIndex(5); 这里的size_x就是一行一共有多少个士兵(Thread),例如上图,这里一行有3个block每一个block里面的每一行有5个Thread,所以size_x就应该为3×5=15,一个Grid的一行有15个士兵,那刚才叫道的那个人的线性编号就应该……
李求斌:不要气馁,加油
wgbljl:加-keep选项生成.cubin文件,这个文件里有
kclau:你好,
我想請問以下,如何知道每一個thread用多少registers呢?
Gemin:呵呵 支持开勇
文章分类
收藏
    相册
    相册
    自己
    参与的开源项目
    Scilab Robotics Toolbox
    常去的几个地方
    Robocup 机器人足球官方站点
    RTSJ
    感兴趣的开源项目
    Flash得开源代码
    Robocup server
    存档
    软件项目交易
    订阅我的博客
    XML聚合  FeedSky
    订阅到鲜果
    订阅到Google
    订阅到抓虾
    订阅到BlogLines
    订阅到Yahoo
    订阅到GouGou
    订阅到飞鸽
    订阅到Rojo
    订阅到newsgator
    订阅到netvibes

    原创 CUDA编程接口(一)------一十八般武器------GPU的革命收藏

    新一篇: CUDA编程接口(二)------一十八般武器------GPU的革命 | 旧一篇: 推荐几本书---GPU,并行算法,多核

     

    CUDA编程接口(一)------一十八般武器
    ------GPU的革命
    序言:所谓一十八般武器,不同的年代又有不同的说法,最早的汉武年间的:矛、镗、刀、戈、槊、鞭、锏、剑、锤、抓、戟、弓、钺、斧、牌、棍、枪、叉。到三国的:九长:刀、矛、戟、槊、镗、钺、棍、枪、叉;九短:斧、戈、牌、箭、鞭、剑、锏、锤、抓。再到明清的:弓、弩、枪、刀、剑、矛、盾、斧、钺、戟、黄、锏、挝、殳(棍)、叉、耙头、锦绳套索、白打(拳术)。《水浒传》里的:矛、锤、弓、弩、铳、鞭、锏、剑、链、挝、斧、钺、戈、戟、牌、棒、枪、扒。今天的武术届又有:刀、枪、剑、戟、斧、钺、钩、叉、鞭、锏、锤、抓、镗、棍、槊、棒、拐、流星。400多种古代冷兵器时代的武器,常用的也只有这么多种。也就像我们的API一样,API有无数多个,你自己都可以给自己造几个API出来,常用的,或者就那么多个。要打天下也不能扛着锄头,竹竿干吧。秦国之所以能统一六国,在武器上的统一,提供同一个的型号的武器装备(看秦的历史,就可以发现所有的兵器都是同一型号生产,弓弩上的器件可以互换,从兵马俑坑中找到的剑戟,箭头的尺寸误差很小,都可以互换),也是他能战胜其他六国的很好的基础。
    正文:
    子曰:工欲善其事,必先利其器。我们要把显卡作为通用并行处理器来做并行算法处理,就得知道CUDA给我提供了什么样的接口,就得了解CUDA作为通用高性能计算平台上的一十八般武器。(如果你想自己开发驱动,自己写开发库- -那我不得不佩服你很有时间,想必也不会有很多人想自己在去实现一个CUDA吧,呵呵,虽然实现一个也不是太难)。书接上回 CUDA硬件实现分析(二)------规行矩步------GPU的革命前面我们讲到了一些简单的CUDA的C语言扩展的规则,下面就具体来讲解CUDA给我听哦买提供了多少方便的API函数。在开发CUDA的时候,CDUA也给我们提供了一套完整的API函数。从一开始就在想,怎么把这些枯燥的API函数,或者这CUDA的一十八般武器说得清楚。如果按照中文的翻译的第四章那么讲解,或许晕的人更多,只知道这些是武器,而不知道什么武器是用来干嘛的。从序言看到,十八般武艺所列兵器大同小异,形式和内容却十分丰富。有长器械,短器械;软器械、双器械;有带钩的、带刺的、带尖的、带刀的;有明的、暗的;有攻的、防的;有打的、杀的击的;也有射的、挡的。我们来看CUDA的时候,一看到这么多的API函数,先来给他分一些类,然后才好徐徐道来。
    一、    API总结:
    1.通用的一些接口,前一章节也有提高过:数学函数,时间函数,同步函数,原子操作;
    2.控制Device的函数;就是得到设备信息,管理设备信息的函数。设置那块显卡工作,得到那块显卡的性能。这里有分为driver级别的API和runtime级别的API;有人会问什么是driver级别和runtime级别请看图:
    这个图我们在前一章已经看到过了。不会不清楚吧~- -!driver级别的API就是提供驱动级别的API,就像写驱动一样的感觉。Runtime级别的API就是封装了一些Driver级别的API,按照一些常规的方法封装了一些底层的API。其实这里就像我们平时生活中一样,最开始对汽车不熟悉的时候,买一辆车回来能开就ok了;能用熟悉Runtime级别的API就行了。慢慢的,感觉汽车自带的音箱不好,自己就开始买一些原始设备回来改装车;慢慢的感觉整车都有点不爽了,然后慢慢的发现想修改发动机,修改外形……就开始改装车了,这样的工作,就得从Driver级别开始做了,玩得更高级一些的就自己设计图纸,自己来用一些零部件来组装车了。这就是Driver API和Runtime API的关系。Runtime的在开始的用起来一般都比较方便,慢慢的发现如果高层(high-level)的Runtime API用起来不方便,就用底层(low-level)的Driver API来自己做改装的车……
    3.内存管理,host的内存,device的内存,global的内存,constant第,shared的,这里会分出来一章单独讲Texture(纹理)内存的使用,说实在的Texture也是内存~非要搞那么神秘,没办法,也只好拿出来单讲……PS:内存管理也分为Runtime级别的API接口,和Device级别的API接口。
    4.程序运行控制:像Stream,Event,Context, Module, Execution control这样的咱都把归类到运行管理的。这里也得分清楚有Runtime级别的,也有Driver级别的。
    5.好了,这里就剩下OpenGL和Direct3D的接口函数了,其实把,这也是为了方便做图来用的,主要是OpenGL和DX都已经成了图形显示方面的标准,so~显卡也得照顾这两个东东了,要不然显卡自己画……hoho要是真自己再来实现OpenGL或者DX,CUDA就真的会头大了,hoho~~还好就借用现有的图形显示的程序来做就行了。牛顿人家都说是站在“牛头人”(巨人)的肩膀上才能看得更远……咱也不要非自己费那么大的经去做一些无用功。想想吃不饱的时候没看到多少人减肥的,倒是现在吃得好了,减肥的人多了……长胖了去健身房减肥,始终感觉有点怪怪的……拿钱去做无用功,(有用功就是减肥)不过现在也有人在做实验,把多余的这些减肥的人的能量转化为电力……扯远了……提一下:他也有两个层次的API,有Runtime层次的,也有Driver层次的。
    好了,差不多就这5个部分的API了。下面我们就来个个讲解CUDA的十八般武器。
    二、    API讲解
    记得小时候练过几天棍法,现在好像起式都记不起来啦,哈哈,只记得棍要齐眉,剑齐耳朵,不知道错没,hoho。大学的时候一直想给武术队的张锐学九节鞭,不过一直太忙,除了和他练习过几次散打对抗,看他刀+鞭,双刀,枪,剑,九节鞭练习过几次以外,到大学毕业,也都没机会学九节鞭,~现在也就留着那一根九节鞭在身边。看到它就能想起一些朋友吧:)大学毕业也就各奔东西,都不知道他现在在干嘛……
    又走神了……想想前面五个部分,下面一部分一部分的讲解:
    1. 一些通用的函数:
    数学函数,前面章节已经讲了,提一下CUDA 2.0里面添加了一两个新的函数。
    时间函数,clock()这个自己去查C的函数库;还有CUDA提供的几个时间函数,用起来也没什么难的,只是说一些,CUDA提供的几个时间函数在计算Device上的运行时间的时候,和CPU上的时间函数比起来还不是那么的完美的准确,所以在做时间数据的时候,最好多取几次求个平均。
    同步函数前面章节讲到了, __syncthreads()函数,原子操作函数也就和以前的原子操作函数一个道理,也不用多加解释。不过其实我都觉得这__syncthreads()函数都应该归到程序运行控制部分。
    2.Device管理
    Runtime和Driver层面的API都提供了设备管理的函数,其实两个层面的API提供的功能都差不多,可以在API的说明中查到他的区别。这里需要指出来的是虽然Host主机上的多线程程序是应该可以同时访问同一块显卡(Device)的,毕竟显卡就是按照PCIE标准插在PCI插槽上的标准设备(也有AGP接口的),这就是一个多线程访问硬件的问题了。So~本来是应该可以的。但是由于CUDA的设计原因,这里的host上的当个多线程的线程每个线程都要执行CUDA kernel的时候,就必须执行在多个Device上面。保证每个线程访问的Device不是同一个。多个线程线程A不能分享线程B在Device上创建的资源。
    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);
    }
    3.内存管理:
    Device上的内存可以被分配成线性的,也可以分配为CUDA的数组形式的。CUDA的内存可以为1维,2维,还有3维(2.0版本)。内存的类型有unsigned8,16或者32位的int,16位(只有driver API可以做到)float,32位的float。这里分配的内存也只能通过kernel里面的函数通过处理纹理的方法来处理。这个地方也是GPU的历史原因了,以前都是处理图像的,所以这里叫纹理。……叫啥都是别人取得名字 - -!在计算机里面不就是内存嘛 - -!
    Host的runtime的运行库也提供按照page-locked的内存管理的函数,page-locked的内存要比pageable方式快很多。好的东西往往比较少~page-locked就是很稀少的。如果通过减少分配pageable的内存来分配多的page-locked内存,系统需要的分页内存就少了,这也就会让系统的性能降低了。所以在处理这块的时候要合理。
    Runtime API:
    使用 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<float>();
    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(&copyParam, 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(&copyParam);
     
    拷贝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×,心中自然无码。
    再多ps一句:上次和刘汝佳聊天,他准备在新版的书中用java来添加一些代码实现讲解,哈哈~hoho

    发表于 @ 2008年05月12日 01:16:00|评论(loading...)|编辑

    新一篇: CUDA编程接口(二)------一十八般武器------GPU的革命 | 旧一篇: 推荐几本书---GPU,并行算法,多核

    评论

    #fengkp 发表于2008-05-12 11:59:57  IP: 159.226.117.*
    收获!!
    #antimatterworld 发表于2008-05-18 17:02:15  IP: 60.20.28.*
    "先把字符串处理好了,就nb了"
    顶这句话!!
    处理字符串看似简单,实际上想把字符串处理好,那是一 件非常困难的事情。俺学编程快3年了,对字符串的处理,还停留在str = "hellow,world!"的水评上
    哎~~
    #Emma 发表于2008-06-06 22:21:12  IP: 202.108.128.*
    在下述语句中:
    int pitch;
    cudaMallocPitch((void**)&devPtr, &pitch,
    width * sizeof(float), height);

    pitch的类型好像不对,应该是size_t类型的。
    2008-06-07 00:07:31作者回复
    其实int和size_t都是可以看成是int的:)只是size_t是64位的:)
    #yiyiemma 发表于2008-06-07 11:10:30  IP: 202.108.128.*
    device api和runtime api我都试过了,runtime api都没有问题,都可以使用,也能得到结果,但是device api有问题,debug时可以使用,但是发现虽然可以过debug,但是去得不到结果,好像没有使用函数一样的。
    #OpenHero 发表于2008-06-08 13:13:48  IP: 158.182.7.*
    什么结果,debug也可以跟踪过去的,不过device上运行的跟踪不过去。可以看看跟踪的结果
    #yiyiemma 发表于2008-06-08 21:22:52  IP: 202.108.128.*
    我跟踪了一下变量的值,发现都没有任何的变化。就好像函数没有使用就直接跳过了一样的。
    #yiyiemma 发表于2008-06-08 21:25:29  IP: 202.108.128.*
    我跟踪了变量的值,发现使用device api时,变量的值前后并没有任何的变化,就好像没有调用就直接跳过了函数一样的。
    #babysun316 发表于2008-06-27 14:44:19  IP: 137.153.0.*
    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);

    用driver API这样调用函数与直接用
    myKernel〈〈〈512,100〉〉〉(devPtr);
    必有什么好处呢?
    还是这么写只是一个例子?
    2008-06-28 05:28:31作者回复
    一个是标准c语言接口的API函数,一个是扩展的C语言的接口,runtime api封装的是device API,内部大概就是实现的这段代码的功能
    发表评论  


    登录
    Csdn Blog version 3.1a
    Copyright © OpenHero 开勇