CUDPP基本执行过程分析
2015-09-21 06:52
260 查看
最近做一个k-D树的实现,中间过程要使用CUDPP,因为有一点不理解的地方,所以想看看CUDPP的具体实现,本文以CUDPP源码中自带的simpleCUDPP为例,就CUDPP的使用和运行过程作简单的分析。
CUDA和数据等的初始化就不说了,相信使用CUDPP的同学都很清楚的。
CUDPP本身是指CUDA
Data-Parallel Primitives Library,翻译成中文也就是CUDA数据并行原语库。
CUDPP初始化
首先要讲的就是CUDPP的初始化:
先定义了一个CUDPP句柄theCudpp,然后调用函数cudppCreate实例化一个CUDPP,并将句柄返回给theCudpp。
需要注意的是实例化必须要调用任何CUDPP函数之前,这也是很好理解的。另外在使用多GPU的应用中,每一个CUDA上下文都需要实例化一次CUDPP(因为每一个CUDA上下文使用一个独立的CUDPP实例)。现在我们来看看cudppCreate具体做了哪些事情。
看下函数cudppCreate的具体实现:
可见函数先在堆上定义了一个CUDPPManager对象,然后调用该对象的getHandle函数,从函数表面理解是返回一个句柄赋值给传进来的参数theCudpp所指向的句柄,紧接着返回。看到这里可能还有点糊涂,因为还是不明白CUDPPManager具体又是什么呢,getHandle返回的句柄又是什么意义,那么继续看源码:
总结一下CUDPP的初始化:实例化一个CUDPP,也就是生成一个CUDPPManager对象,将其地址(已经转化为size_t类型)返回给theCudpp保存。
CUDPP配置
初始化之后要做的工作就是配置CUDPP,也就是要指定自己要利用CUDPP做什么操作(ADD、MULTIPLY、MIN等等),操作的是什么类型的数据(CHAR、INT、FLOAT等)要采用什么原语(SCAN、SORT、SEGMENTED
SCAN等)还有就是操作的有关选项(FORWORD还是BACKWORD,INCLUSIVE还是EXCLUSIVE)。
配置的代码相对简单也很好理解,下面列出来就好了。
配置完毕,马上就要切入正题。
生成CUDPP计划
先贴代码:
开始还是有点难以理解的,为什么前面生成了一个CUDPPHandle并且配置也完了,不是应该要进行操作了吗,怎么现在又来一个CUDPPHandle呢?语句的表面意思比较好理解,大致可以认为它是定义了一个CUDPPHandle然后生成一个cudppPlan并且将句柄赋值给scanPlan。那theCudpp和scanPlan是什么关系呢?我也是出于这个原因来看具体实现的。下面就看看cudppPlan做的工作吧。
整个函数其实很简单,
就是根据配置创建一个CUDPPPlan,然后将地址值转换为CUDPPHandle赋值给planHandle。
接下来就以CUDPPRadixSortPlan为例看看是怎么建立起cudppHandle和planHandle之间的关系的。
当然先要了解一下CUDPPRadixSortPlan的实现(具体实现省略):
主要看这个继承关系,然后直接贴出CUDPPPLan的实现:
以此可以看出,每一个CUDPPPlan有一个成员变量为CUDPPManager指针,指向CUDPP实例。
再看CUDPPRadixSortPlan构造函数中一个语句(其他都是涉及一些具体实现的内容):
在函数allocRadixSortStorage在分配一些显存空间以后调用函数:initDeviceParameters,这也就是解析cudppHandle和planHandle关系的关键。initDeviceParameters具体实现就是根据planHandle的CUDPPManager指针成员变量,设置在进行原语操作过程中的一些参数的最优值。至此,相信困扰我们的谜团已经解开了。OK,继续下一步骤--执行原语操作!
执行原语
直接看代码吧:
一句话,不解释!直接分析中间存在的导致混淆的一点小问题:在这个简单的cudpp例子中,整个过程计划中(按照配置config参数的设置)和最终执行的原语,都是scan操作,而实际上,在最后这一步执行原语的时候可以调用其他的原语操作,例如cudppReduce、
cudppSegmentedScan等等,只是在这些函数实现中会进行一次配置的判断,如果不匹配就会报错[相信CUDPP的设计者也是做了详尽的考虑,采用了这种最简洁高效的方式]。
清理
清理工作是必须的,因为每一个CUDPPHandle都占用了一定的系统资源。需要清理的内容有两个类型:
1)CUDPP计划:cudppDestroyPlan(scanPlan)
2)CUDPP实例:cudppDestroy(theCudpp)
清理顺序:最好是先清理CUDPP计划,然后清理CUDPP实例,当然在保证正确性的情况下顺序无关紧要。
至此CUDPP的基本使用过程就完了,下面做个小结。
小结
以解答问题的方式来作总结吧,这也是我一开始不清楚的几个问题:
CUDPP实例和CUDPP计划的功能分别是什么?两者有什么关系?
CUDPP实例保存了一个CUDPPManager对象的指针,该对象保存了一个CUDA设备的属性值。CUDPP计划会根据配置参数config生成执行原语的最优参数并分配一系列临时显存用于执行原语操作,在计算最优配置参数的过程中需要结合CUDPP实例中的CUDA设备属性,这也是CUDPP实例和CUDPP计划的关系。
一个利用CUDPP的应用可否具有多个CUDPP实例和CUDPP计划?如何配置?
从CUDPP的功能上可以看出在只具有一个CUDA设备的计算机上,只需要创建一个CUDPP实例,在具有多CUDA设备的计算机上可以实例化多个CUDPP,然后可以对不同的CUDPP计划采用不同的实例,以表示该计划运行于不同的CUDA设备上。
CUDPP计划当然可以存在多个,不同的计划执行不同的并行原语,每个计划需要独立生成和销毁。
配置也很简单了,可以基于一个CUDPP实例,生成多个CUDPP计划,每个CUDPP计划必须关联到一个并仅有一个CUDPP实例,可以说CUDPP实例和CUDPP计划的对应关系是1对多的关系。
CUDA和数据等的初始化就不说了,相信使用CUDPP的同学都很清楚的。
CUDPP本身是指CUDA
Data-Parallel Primitives Library,翻译成中文也就是CUDA数据并行原语库。
CUDPP初始化
首先要讲的就是CUDPP的初始化:
CUDPPHandle theCudpp; cudppCreate(&theCudpp);
先定义了一个CUDPP句柄theCudpp,然后调用函数cudppCreate实例化一个CUDPP,并将句柄返回给theCudpp。
需要注意的是实例化必须要调用任何CUDPP函数之前,这也是很好理解的。另外在使用多GPU的应用中,每一个CUDA上下文都需要实例化一次CUDPP(因为每一个CUDA上下文使用一个独立的CUDPP实例)。现在我们来看看cudppCreate具体做了哪些事情。
看下函数cudppCreate的具体实现:
CUDPPResult cudppCreate(CUDPPHandle* theCudpp) { CUDPPManager *mgr = new CUDPPManager(); *theCudpp = mgr->getHandle(); return CUDPP_SUCCESS; }
可见函数先在堆上定义了一个CUDPPManager对象,然后调用该对象的getHandle函数,从函数表面理解是返回一个句柄赋值给传进来的参数theCudpp所指向的句柄,紧接着返回。看到这里可能还有点糊涂,因为还是不明白CUDPPManager具体又是什么呢,getHandle返回的句柄又是什么意义,那么继续看源码:
class CUDPPManager{public: //无关的函数先省了 CUDPPHandle getHandle() { return reinterpret_cast<CUDPPHandle>(this); } private: cudaDeviceProp m_deviceProps;}; 看到这里,相信我们应该都会明白了,原来返回的句柄就是指向CUDPPManager的对象mgr本身的地址值,也就是说这个句柄实际上是一个指向CUDPPManager对象的地址。而这个对象具有一个cudaDeviceProp的成员变量,每一个CUDA上下文都需要实例化一次CUDPP的原因也就在于他的成员变量上(CUDPP会根据设备,也就是显卡的属性值自动的选择最优的参数进行原语操作)。这里还涉及到C++类型转化操作符reinterpret_cast的使用,简单地说它的一种用法是对指针和一个整形数(这也就是CUDPPHandle的实际类型size_t)进行互相转换,具体使用可以再查资料。
总结一下CUDPP的初始化:实例化一个CUDPP,也就是生成一个CUDPPManager对象,将其地址(已经转化为size_t类型)返回给theCudpp保存。
CUDPP配置
初始化之后要做的工作就是配置CUDPP,也就是要指定自己要利用CUDPP做什么操作(ADD、MULTIPLY、MIN等等),操作的是什么类型的数据(CHAR、INT、FLOAT等)要采用什么原语(SCAN、SORT、SEGMENTED
SCAN等)还有就是操作的有关选项(FORWORD还是BACKWORD,INCLUSIVE还是EXCLUSIVE)。
配置的代码相对简单也很好理解,下面列出来就好了。
CUDPPConfiguration config; config.op = CUDPP_ADD; config.datatype = CUDPP_FLOAT; config.algorithm = CUDPP_SCAN; config.options = CUDPP_OPTION_FORWARD | CUDPP_OPTION_EXCLUSIVE;
配置完毕,马上就要切入正题。
生成CUDPP计划
先贴代码:
CUDPPHandle scanplan = 0; CUDPPResult res = cudppPlan(theCudpp, &scanplan, config, numElements, 1, 0);
开始还是有点难以理解的,为什么前面生成了一个CUDPPHandle并且配置也完了,不是应该要进行操作了吗,怎么现在又来一个CUDPPHandle呢?语句的表面意思比较好理解,大致可以认为它是定义了一个CUDPPHandle然后生成一个cudppPlan并且将句柄赋值给scanPlan。那theCudpp和scanPlan是什么关系呢?我也是出于这个原因来看具体实现的。下面就看看cudppPlan做的工作吧。
CUDPPResult cudppPlan(const CUDPPHandle cudppHandle, CUDPPHandle *planHandle, CUDPPConfiguration config, size_t numElements, size_t numRows, size_t rowPitch) { CUDPPResult result = CUDPP_SUCCESS; //从CUDPPManager的实现中可以看到下面两句是将cudppHandle转换回CUDPPManager对象指针 CUDPPPlan *plan; CUDPPManager *mgr = CUDPPManager::getManagerFromHandle(cudppHandle); //这一段也很好理解,就是判断配置是否正确,具体可以看validateOptions函数实现。 result = validateOptions(config, numElements, numRows, rowPitch); if (result != CUDPP_SUCCESS) { *planHandle = CUDPP_INVALID_HANDLE; return result; } //接下来就要真正开始创建plan了,创建基于配置参数config,生成不同类型的并行原语 switch (config.algorithm) { case CUDPP_SCAN: { plan = new CUDPPScanPlan(mgr, config, numElements, numRows, rowPitch); break; } // ... //中间一些case以及default项就省了 // ... } //最后不多解释了,将CUDPPPlan对象指针转换为CUDPPHandle赋值给planHandle,返回 if (!plan) return CUDPP_ERROR_UNKNOWN; else { *planHandle = plan->getHandle(); return CUDPP_SUCCESS; } }
整个函数其实很简单,
就是根据配置创建一个CUDPPPlan,然后将地址值转换为CUDPPHandle赋值给planHandle。
接下来就以CUDPPRadixSortPlan为例看看是怎么建立起cudppHandle和planHandle之间的关系的。
当然先要了解一下CUDPPRadixSortPlan的实现(具体实现省略):
class CUDPPRadixSortPlan: public CUDPPPlan
主要看这个继承关系,然后直接贴出CUDPPPLan的实现:
class CUDPPPlan { public: //其他无关项省去 CUDPPManager *m_planManager; };
以此可以看出,每一个CUDPPPlan有一个成员变量为CUDPPManager指针,指向CUDPP实例。
再看CUDPPRadixSortPlan构造函数中一个语句(其他都是涉及一些具体实现的内容):
allocRadixSortStorage(this);
在函数allocRadixSortStorage在分配一些显存空间以后调用函数:initDeviceParameters,这也就是解析cudppHandle和planHandle关系的关键。initDeviceParameters具体实现就是根据planHandle的CUDPPManager指针成员变量,设置在进行原语操作过程中的一些参数的最优值。至此,相信困扰我们的谜团已经解开了。OK,继续下一步骤--执行原语操作!
执行原语
直接看代码吧:
res = cudppScan(scanPlan,pOutData,pInData, g_MyData.size); if(CUDPP_SUCCESS != res) std::cout<<"Operate ERROR!"<<std::endl;
一句话,不解释!直接分析中间存在的导致混淆的一点小问题:在这个简单的cudpp例子中,整个过程计划中(按照配置config参数的设置)和最终执行的原语,都是scan操作,而实际上,在最后这一步执行原语的时候可以调用其他的原语操作,例如cudppReduce、
cudppSegmentedScan等等,只是在这些函数实现中会进行一次配置的判断,如果不匹配就会报错[相信CUDPP的设计者也是做了详尽的考虑,采用了这种最简洁高效的方式]。
清理
清理工作是必须的,因为每一个CUDPPHandle都占用了一定的系统资源。需要清理的内容有两个类型:
1)CUDPP计划:cudppDestroyPlan(scanPlan)
2)CUDPP实例:cudppDestroy(theCudpp)
清理顺序:最好是先清理CUDPP计划,然后清理CUDPP实例,当然在保证正确性的情况下顺序无关紧要。
至此CUDPP的基本使用过程就完了,下面做个小结。
小结
以解答问题的方式来作总结吧,这也是我一开始不清楚的几个问题:
CUDPP实例和CUDPP计划的功能分别是什么?两者有什么关系?
CUDPP实例保存了一个CUDPPManager对象的指针,该对象保存了一个CUDA设备的属性值。CUDPP计划会根据配置参数config生成执行原语的最优参数并分配一系列临时显存用于执行原语操作,在计算最优配置参数的过程中需要结合CUDPP实例中的CUDA设备属性,这也是CUDPP实例和CUDPP计划的关系。
一个利用CUDPP的应用可否具有多个CUDPP实例和CUDPP计划?如何配置?
从CUDPP的功能上可以看出在只具有一个CUDA设备的计算机上,只需要创建一个CUDPP实例,在具有多CUDA设备的计算机上可以实例化多个CUDPP,然后可以对不同的CUDPP计划采用不同的实例,以表示该计划运行于不同的CUDA设备上。
CUDPP计划当然可以存在多个,不同的计划执行不同的并行原语,每个计划需要独立生成和销毁。
配置也很简单了,可以基于一个CUDPP实例,生成多个CUDPP计划,每个CUDPP计划必须关联到一个并仅有一个CUDPP实例,可以说CUDPP实例和CUDPP计划的对应关系是1对多的关系。
相关文章推荐
- viewpager不显示
- 写几十行代码,来一场无鼠标编程之旅,看看who is e———(HTML5:HBuilder5.0.0)
- 在win7系统的电脑上安装CentOS7双系统
- 2015年9月14日--9月20日(30小时,剩3485小时)
- UIPercentDrivenInteractiveTransition Controller交互式转场切换动画
- 新的开始
- android给未签名的apk签名
- C++类的const, static 和inline成员函数(变量)
- 如何获取帮助———— QQ群讨论摘要
- Best Time to Buy and Sell Stock II 解答
- 【软剑攻城队】团队介绍发布!
- 内核随记(一)——理解中断(2)
- [LeetCode]Different Ways to Add Parentheses
- 人类是否能够接受人工智能(AI)的存在?
- php 参数传递
- 内核随记(一)——理解中断(1)
- LeetCode Reverse Words in a String
- LeetCode Simplify Path
- linux0.11下的中断机制分析
- Android:Activity生命周期