前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >opencl:改造C++接口增加对内存编译(compile)的支持

opencl:改造C++接口增加对内存编译(compile)的支持

作者头像
10km
发布2019-05-25 22:42:15
8770
发布2019-05-25 22:42:15
举报
文章被收录于专栏:10km的专栏10km的专栏

版权声明:本文为博主原创文章,转载请注明源地址。 https://cloud.tencent.com/developer/article/1433788

OpenCL 1.2以前的标准(1.0,1.1),只支持单个源文件编译成可执行程序(Executable Program),所以只提供了clBuildProgram函数。

OpenCL 1.2以后,可以将complie/link两个动作分开,增加了clCompileProgram, clLinkProgram函数,允许将多个源码编译成一个可执行程序。

clCompileProgram将一段内核代码编译成非可执行的cl::Progam对象(类似于obj文件)。

clLinkProgram则可以将多个obj对象连接生成新的可执行的cl::Program对象(Executable Program)。

下面是clCompileProgram 的函数定义:

代码语言:javascript
复制
cl_int clCompileProgram (   cl_program program,
    cl_uint num_devices,
    const cl_device_id *device_list,
    const char *options,// 编译选项字符串
    cl_uint num_input_headers, // 代码#include的文件数目
    const cl_program *input_headers,// 每个#include的文件对应的cl_program对象(由clCreateProgramWithSource生成)
    const char **header_include_names,//与input_headers对应的每个cl_program对象在代码中的#include<文件名>
    void (CL_CALLBACK *pfn_notify)( cl_program program, void *user_data),
    void *user_data)

假设一段内核源码中有#include语句,导入了一个头文件定义,那么OpenCL编译器该从哪里找这些头文件呢?

有两种方法:

  1. 在options指定的编译选项中加入-I path 选项,告诉编译器在path指定的路径下寻找#include文件
  2. 将内核源码中所有#include文件内容转成cl_program,以数组形式提供作为input_headers参数,同时将每个#include的文件名作为名字表以数组形式提供作为header_include_names(与input_headers一一对应),这样编译就会从这张表中找到代码中每个#include文件的内容了。

如果在编译代码时以上两个方法都使用了,编译器优先使用方法2提供的头文件

第一种方法很常用也很容易理解,就跳过不说了,这里要着重说明的是第二种编译方法的意义:

clCompileProgram在编译一段OpenCL内核源码(字符串)时,源码中所#include的文件内容可以像源码本身一样不必存在于本地文件系统(硬盘/存储卡),也就是不依赖文件系统只依赖内存的编译,所以在嵌入式系统或网络应用中这种方式适应性更好。

原本我的项目中是打算使用第二种方式来编译源码的。但打开OpenCL 1.2的C++接口代码(cl.hpp)找到clCompileProgram对应的cl::Program::compile成员函数一看,傻了:

代码语言:javascript
复制
#if defined(CL_VERSION_1_2)
    cl_int compile(
        const char* options = NULL,
        void (CL_CALLBACK * notifyFptr)(cl_program, void *) = NULL,
        void* data = NULL) const
    {
        return detail::errHandler(
            ::clCompileProgram(
                object_,
                0,
                NULL,
                options,
                0,
                NULL,
                NULL,
                notifyFptr,
                data),
                __COMPILE_PROGRAM_ERR);
    }
#endif

上面的代码可以看出cl::Program::compile在调用clCompileProgram的时候,直接将num_input_headers置为0,将 input_headers,header_include_names置为NULL了。也就是说Open CL C++接口没有提供第二种引入#include的编译方式,尼玛,你故意的吧?!

所以基于OpenCL C++接口开发,且需要进行内核源码的内存编译的情况下,需要自己写compile函数,实现这部分功能,我的办法是继承cl::Program写个新的类ProgramExt,增加一个支持内存编译compile函数:

下面是complie函数源码:

代码语言:javascript
复制
#define _DEF_STRING(x) #x
// 内核源码描述类 pair.first为源码名字,pair.second为源码对象(cl::Progam)
using program_info_type =std::pair<std::string,cl::Program>;
class ProgramExt:public cl::Program{
    using cl::Program::Program; // 继承cl::Program所有的构造函数
#if defined(CL_VERSION_1_2)
    cl_int compile(
        const std::vector<cl::Device> &devices,
        const std::vector<program_info_type> &input_headers,
        const char* options = NULL,
        void (CL_CALLBACK * notifyFptr)(cl_program, void *) = NULL,
        void* data = NULL) const
{
    // 生成cl_device_id数组
    auto deviceIDs=cl::cl_c_vector(devices);//cl_c_vector函数实现参见下面的代码
    // 生成cl_program数组
    auto headers=cl::cl_c_vector2(input_headers);//cl_c_vector2函数实现参见下面的代码
    // 生成头文件名字数组
    auto include_names=cl::cl_c_vector1(input_headers);//cl_c_vector1函数实现参见下面的代码
    return cl::detail::errHandler(
        ::clCompileProgram(
            object_,
            (cl_uint)deviceIDs.size(),
            deviceIDs.size()?deviceIDs.data():nullptr,
            options,
            (cl_uint)headers.size(),
            // headers.size()为0时input_headers设置为nullptr
            headers.size()?headers.data():nullptr,
            // headers.size()为0时header_include_names设置为nullptr
            (const char**)(headers.size()?include_names.data():nullptr),
            notifyFptr,
            data),
            _DEF_STRING(clCompileProgram));
}        
#endif
};
// 上面代码中用到的支持函数cl_c_vector,cl_c_vector1,cl_c_vector2模板函数的实现代码
namespace cl{
/* 将OpenCL C++对象数组转为对应的C对象数组 */
template<typename F,typename T=typename F::cl_type>
std::vector<T> cl_c_vector(const std::vector<F> &from){
    std::vector<T> v(from.size());
    for( auto i = from.size(); i >0; --i ) {
        v[i-1] = from[i-1]();
    }
    return std::move(v);
}
/* 拆分std::pair,返回pair::first数组 */
template<typename F1,typename F2 >
std::vector<F1> cl_c_vector1(const std::vector<std::pair<F1,F2>> &from){
    auto v=std::vector<F1>(from.size());
    for(auto i=from.size();i>0;--i){
        v[i-1]=from[i-1].first;
    }
    return std::move(v);
}
/* 拆分std::pair,返回pair::first数组(char*) */
template<typename F2 >
std::vector<char*> cl_c_vector1(const std::vector<std::pair<std::string,F2>> &from){
    auto v=std::vector<char*>(from.size());
    for(auto i=from.size();i>0;--i){
        v[i-1]=(char*)from[i-1].first.data();
    }
    return std::move(v);
}
/* 拆分std::pair,返回pair::second(OpenCL C++对象)数组并转为C对象数组 */
template<typename F1,typename F2 ,typename T=typename F2::cl_type>
std::vector<T> cl_c_vector2(const std::vector<std::pair<F1,F2>> &from){
    auto v=std::vector<T>(from.size());
    for(auto i=from.size();i>0;--i){
        v[i-1]=from[i-1].second();
    }
    return std::move(v);
}
} /* namespace cl */

关于如何调用OpenCL C++接口编译内核代码的更详细内容,参见我的上一篇博客《C++代码设计:向Java借鉴Builder模式塈OpenCL内核代码编译》

(以上所有代码都为C++11撰写)

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2016年03月03日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档