この記事は、インテル® デベロッパー・ゾーンに公開されている「opencl_program and argument binding」(https://software.intel.com/en-us/blogs/2015/12/16/opencl-program-and-argument-binding/) の日本語参考訳です。
この記事は、インテル® スレッディング・ビルディング・ブロック (インテル® TBB) 4.4 Update 2 以降で利用可能な新しいノード opencl_node について説明するシリーズのパート 4 です。このノードは、インテル® TBB のフローグラフで OpenCL* デバイスの利用と連携を可能にします。このシリーズの以前の記事は、opencl_node の概要 (パート 1)、opencl_node の基本インターフェイス (パート 2)、opencl_node でのデバイスの選択 (パート 3) をご覧ください。
パート 4 では、OpenCL* プログラムを指定し、プログラムからカーネルを選択して、カーネル呼び出しに引数をバインドする方法を説明します。
opencl_program
以前の例では、OpenCL* C ソースコードで提供される opencl_node を使用しました。opencl_node は、SPIR とプリコンパイル済みカーネルもサポートします。プログラムの種類を指定するには、opencl_program クラスを使用します。
enum class opencl_program_type { SOURCE, PRECOMPILED, SPIR }; template <typename Factory = default_opencl_factory> class opencl_program { public: opencl_program( opencl_program_type type, const std::string& program_name ); };
SPIR とプリコンパイル済みカーネルは、インテル® SDK for OpenCL* Applications に含まれる Kernel Builder for OpenCL* API を利用して作成できます。
- SPIR カーネルを作成するには、次のコマンドを実行します。
ioc64 -cmd=build -input=hello_world.cl -spir64=hello_world.spir -bo="-cl-std=CL1.2"
- インテル® プロセッサー・グラフィックス向けのプリコンパイル済みカーネルを作成するには、次のコマンドを実行します。
ioc64 -cmd=build -input=hello_world.cl -ir=hello_world.clbin -bo="-cl-std=CL1.2" -device=gpu
次の例は、SPIR とプリコンパイル済みカーネルの使用法を示します。
#define TBB_PREVIEW_FLOW_GRAPH_NODES 1 #include "tbb/flow_graph_opencl_node.h" #include <algorithm> int main() { using namespace tbb::flow; opencl_graph g; opencl_device_list dl = g.available_devices(); opencl_device_list::iterator it = std::find_if( dl.begin(), dl.end(), []( opencl_device d ) -> bool { // SPIR 対応のインテル(R) プロセッサー・グラフィックスを搭載した // デバイスの選択 cl_uint bitness; d.info( CL_DEVICE_ADDRESS_BITS, bitness ); bool isSpir = d.extension_available("cl_khr_spir") && bitness == 64; bool isPrecompiled = d.platform_name() == "Intel(R) OpenCL" && d.type() == CL_DEVICE_TYPE_GPU; return isSpir && isPrecompiled; } ); if ( it == dl.end() ) return -1; g.opencl_factory().init( { *it } ); // SPIR カーネルを使用する opencl_program と opencl_node の作成 opencl_program<> spirProgram( opencl_program_type::SPIR, "hello_world.spir" ); opencl_node<tuple<opencl_buffer<cl_char>>> clPrintSPIR( g, spirProgram, "print" ); // プリコンパイル済みカーネルを使用する opencl_node の作成 opencl_node<tuple<opencl_buffer<cl_char>>> clPrintPrecompiled( g, { opencl_program_type::PRECOMPILED, "hello_world.clbin" }, "print" ); const char str[] = "Hello, World!"; opencl_buffer<cl_char> b( g, sizeof(str) ); std::copy_n( str, sizeof(str), b.begin() ); clPrintSPIR.set_ndranges( { 1 } ); clPrintPrecompiled.set_ndranges( { 1 } ); std::cout << "SPIR kernel: "; input_port<0>(clPrintSPIR).try_put( b ); g.wait_for_all(); std::cout << "Precompiled kernel: "; input_port<0>(clPrintPrecompiled).try_put( b ); g.wait_for_all(); return 0; }
出力は次のとおりです。
SPIR kernel: OpenCL says 'Hello, World!' Precompiled kernel: OpenCL says 'Hello, World!'
カスタム factory を使用する場合は、そのカスタム factory で opencl_program をインスタンス化する必要があります。
typedef opencl_factory<MyDeviceFilter> MyFactory; opencl_program<MyFactory> p(opencl_program_type::SPIR, "hello_world.spir");
引数のバインド
一般に、OpenCL* カーネルには多くの引数が渡されます。デフォルトでは、opencl_node は 1 つ目の入力ポートを 1 つ目のカーネル引数に、2 つ目の入力ポートを 2 つ目のカーネル引数に … というようにバインドします。次の例は、値 ‘a’、2.0f、および 3 をカーネルに渡し、カーネルはそれぞれ char、float、int として受け取ります。
#define TBB_PREVIEW_FLOW_GRAPH_NODES 1 #include "tbb/flow_graph_opencl_node.h" int main() { using namespace tbb::flow; opencl_graph g; opencl_node<tuple<cl_char, cl_float, cl_int>> clNode( g, "several_arguments.cl", "foo" ); clNode.set_ndranges( { 1 } ); input_port<0>(clNode).try_put( 'a' ); input_port<1>(clNode).try_put( 2.0f ); input_port<2>(clNode).try_put( 3 ); g.wait_for_all(); return 0; }
several_arguments.cl:
kernel void foo ( char c, float f, int i ) { printf("OpenCL says: 'kernel arguments are `%c`, %f, %d'\n", c, f, i); }
出力は次のとおりです。
OpenCL says: 'kernel arguments are `a`, 2.000000, 3'
引数が常に一定の場合、実行ごとに値を指定する必要はありません。opencl_node の set_args メソッドを使用して、カーネルの特定の引数に値をバインドできます。同様に、N 番目の入力ポートを返す port_ref<N>() ヘルパーを使用して、入力ポートをカーネル引数にバインドすることができます。 次の例は、カーネルの第 1 引数を input_port<0> から取得し、第 2 引数と第 3 引数には値 2.0f と 3 をそれぞれ指定します。
clNode.set_args( port_ref<0>(), 2.0f, 3 ); input_port<0>(clNode).try_put( 'b' ); input_port<1>(clNode).try_put( 3.0f ); input_port<2>(clNode).try_put( 4 ); g.wait_for_all();
出力は次のとおりです。
OpenCL says: 'kernel arguments are `b`, 2.000000, 3'
バインドにより input_port<0> のみが使用されるため、2 つ目と 3 つ目のポートに渡されたメッセージは無視されます。ただし、opencl_node では、メッセージを実行するためにはすべての input_port にメッセージを送る必要があります。不要なポートは、次のように opencl_node の宣言で省略することができます。
opencl_node<tuple<cl_char>> clNode2( g, "several_arguments.cl", "foo" ); clNode2.set_ndranges( { 1 } ); clNode2.set_args( port_ref<0>(), 2.0f, 3 ); input_port<0>(clNode).try_put( 'b' ); g.wait_for_all();
clNode2 has only one port but a kernel will be executed with three arguments: `b`, 2.0f and 3.
ポートの範囲を指定するには、port_ref<BEGIN,END>() を使用します (END は範囲に含まれます)。
clNode.set_args( port_ref<0,2>() ); input_port<0>(clNode).try_put( 'c' ); input_port<1>(clNode).try_put( 4.0f ); input_port<2>(clNode).try_put( 5 ); g.wait_for_all();
出力は次のとおりです。
OpenCL says: 'kernel arguments are `c`, 4.000000, 5'
opencl_node 実行ごとに ndrange を指定する場合、set_ndranges とともに port_ref を使用することもできます。
#define TBB_PREVIEW_FLOW_GRAPH_NODES 1 #include "tbb/flow_graph_opencl_node.h" #include <string> #include <algorithm> #include <list> int main() { using namespace tbb::flow; opencl_graph g; opencl_node<tuple<opencl_buffer<cl_char>,std::list<size_t>>> clPrint( g, "hello_world.cl", "print" ); clPrint.set_ndranges( port_ref<1>() ); clPrint.set_args( port_ref<0>() ); char str[] = "Hello, World!"; opencl_buffer<cl_char> b( g, sizeof(str) ); std::copy_n( str, sizeof(str), b.begin() ); input_port<0>(clPrint).try_put( b ); std::list<size_t> ndrange; ndrange.push_back( 1 ); input_port<1>(clPrint).try_put( ndrange ); g.wait_for_all(); return 0; }
ndrange が入力ポートにバインドされている場合は、明示的に引数をバインドする必要があります。
このシリーズの次の記事では、順序付けの問題を回避するため、型指定したメッセージキーの使用法について述べます。
コンパイラーの最適化に関する詳細は、最適化に関する注意事項を参照してください