Opencl:C++インタフェースを改造しメモリコンパイル(compile)のサポートを追加


OpenCL 1.2以前の規格(1.0,1.1)では、単一ソースファイルの実行可能プログラムへのコンパイルのみがサポートされていたため、clBuildProgram関数のみが提供された.OpenCL 1.2以降、complie/linkの2つの動作を分離することができ、clCompileProgramclLinkProgram関数を追加し、複数のソースコードを1つの実行可能プログラムにコンパイルすることができ、clCompileProgramは1つのコアコードを非実行の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コンパイラはこれらのヘッダファイルをどこから探せばいいのでしょうか.2つの方法があります.
  • optionsで指定されたコンパイルオプションに-I pathオプションを追加し、コンパイラにpathで指定されたパスの下で#includeファイル
  • を探すように伝える.
  • は、カーネルソース内のすべてのincludeファイルの内容をcl_programに変換し、input_headersパラメータとして配列形式で提供するとともに、各includeのファイル名を名前テーブルとしてheader_include_names(input_headersに対応)配列形式で提供し、コンパイルはこのテーブルからコード内の各includeファイルの内容を見つけることができる.

  • 第1の方法はよく使われていて理解しやすいので、スキップして言わないでください.ここで重点的に説明するのは第2のコンパイル方法の意味です.clCompileProgramはOpenCLカーネルのソースコード(文字列)をコンパイルするとき、ソースコードの中のincludeのファイル内容はソースコード自体のようにローカルファイルシステム(ハードディスク/メモリカード)に保存する必要はありません.すなわち、ファイルシステムに依存せず、メモリのコンパイルにのみ依存するため、組み込みシステムやネットワークアプリケーションでは適応性が高い.
    もともと私のプロジェクトでは、ソースコードをコンパイルするために2つ目の方法を使用するつもりでした.しかし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++インタフェースは2つ目の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()-1; i >00; --i ) {
            v[i] = from[i]();
        }
        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()-1;i>=0;--i){
            v[i]=from[i].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()-1;i>=0;--i){
            v[i]=(char*)from[i].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()-1;i>=0;--i){
            v[i]=from[i].second();
        }
        return std::move(v);
    }
    } /* namespace cl */

    OpenCL C++インタフェースを呼び出してカーネルコードをコンパイルする方法については、私の前のブログ「C++コード設計:JavaにBuilderモード
    (以上のすべてのコードはC++11で書かれています)