Opencl:C++インタフェースを改造しメモリコンパイル(compile)のサポートを追加
OpenCL 1.2以前の規格(1.0,1.1)では、単一ソースファイルの実行可能プログラムへのコンパイルのみがサポートされていたため、
カーネルのソースコードに#include文があり、ヘッダファイル定義がインポートされていると仮定すると、OpenCLコンパイラはこれらのヘッダファイルをどこから探せばいいのでしょうか.2つの方法があります. optionsで指定されたコンパイルオプションに-I pathオプションを追加し、コンパイラにpathで指定されたパスの下で#includeファイル を探すように伝える.は、カーネルソース内のすべてのincludeファイルの内容を
第1の方法はよく使われていて理解しやすいので、スキップして言わないでください.ここで重点的に説明するのは第2のコンパイル方法の意味です.clCompileProgramはOpenCLカーネルのソースコード(文字列)をコンパイルするとき、ソースコードの中のincludeのファイル内容はソースコード自体のようにローカルファイルシステム(ハードディスク/メモリカード)に保存する必要はありません.すなわち、ファイルシステムに依存せず、メモリのコンパイルにのみ依存するため、組み込みシステムやネットワークアプリケーションでは適応性が高い.
もともと私のプロジェクトでは、ソースコードをコンパイルするために2つ目の方法を使用するつもりでした.しかしOpenCL 1.2のC++インタフェースコード(cl.hpp)を開いて
上記のコードから、
したがって、OpenCL C++インタフェースに基づいて開発され、カーネルソースのメモリコンパイルが必要な場合は、
次はcomplie関数のソースコードです.
OpenCL C++インタフェースを呼び出してカーネルコードをコンパイルする方法については、私の前のブログ「C++コード設計:JavaにBuilderモード
(以上のすべてのコードはC++11で書かれています)
clBuildProgram
関数のみが提供された.OpenCL 1.2以降、complie/linkの2つの動作を分離することができ、clCompileProgram
、clLinkProgram
関数を追加し、複数のソースコードを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つの方法があります.
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で書かれています)