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

版权声明:本文为博主原创文章,转载请注明源地址。 https://blog.csdn.net/10km/article/details/50789158

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 的函数定义:

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成员函数一看,傻了:

#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函数源码:

#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撰写)

阅读更多

没有更多推荐了,返回首页