CUDPP基本执行过程分析

  •     最近做一个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做什么操作(ADDMULTIPLYMIN等等),操作的是什么类型的数据(CHARINTFLOAT等)要采用什么原语(SCANSORTSEGMENTED SCAN等)还有就是操作的有关选项(FORWORD还是BACKWORDINCLUSIVE还是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。那theCudppscanPlan是什么关系呢?我也是出于这个原因来看具体实现的。下面就看看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为例看看是怎么建立起cudppHandleplanHandle之间的关系的。

        当然先要了解一下CUDPPRadixSortPlan的实现(具体实现省略):

    class CUDPPRadixSortPlan: public CUDPPPlan

    主要看这个继承关系,然后直接贴出CUDPPPLan的实现:

    class CUDPPPlan

    {

    public:

       //其他无关项省去

        CUDPPManager      *m_planManager;

    };

    以此可以看出,每一个CUDPPPlan有一个成员变量为CUDPPManager指针,指向CUDPP实例。

    再看CUDPPRadixSortPlan构造函数中一个语句(其他都是涉及一些具体实现的内容):

    allocRadixSortStorage(this);

        在函数allocRadixSortStorage在分配一些显存空间以后调用函数:initDeviceParameters,这也就是解析cudppHandleplanHandle关系的关键。initDeviceParameters具体实现就是根据planHandleCUDPPManager指针成员变量,设置在进行原语操作过程中的一些参数的最优值。至此,相信困扰我们的谜团已经解开了。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都占用了一定的系统资源。需要清理的内容有两个类型:

    1CUDPP计划:cudppDestroyPlan(scanPlan)

    2CUDPP实例: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对多的关系。


         本文为个人见解,如有谬误,欢迎大家踊跃拍砖~~
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值