您的位置:首页 > 其它

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的初始化:
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对多的关系。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: