この記事は、インテル® デベロッパー・ゾーンに公開されている「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 クラスを使用します。
1 2 3 4 5 6 7 8 9 10 11 12 | 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 カーネルを作成するには、次のコマンドを実行します。
1 | ioc64 -cmd=build -input=hello_world.cl -spir64=hello_world.spir -bo= "-cl-std=CL1.2" |
- インテル® プロセッサー・グラフィックス向けのプリコンパイル済みカーネルを作成するには、次のコマンドを実行します。
1 | ioc64 -cmd=build -input=hello_world.cl -ir=hello_world.clbin -bo= "-cl-std=CL1.2" -device=gpu |
次の例は、SPIR とプリコンパイル済みカーネルの使用法を示します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | #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; } |
出力は次のとおりです。
1 2 | SPIR kernel: OpenCL says 'Hello, World!' Precompiled kernel: OpenCL says 'Hello, World!' |
カスタム factory を使用する場合は、そのカスタム factory で opencl_program をインスタンス化する必要があります。
1 2 | 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 として受け取ります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | #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:
1 2 3 | kernel void foo ( char c, float f, int i ) { printf ( "OpenCL says: 'kernel arguments are `%c`, %f, %d'\n" , c, f, i); } |
出力は次のとおりです。
1 | 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 をそれぞれ指定します。
1 2 3 4 5 | 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(); |
出力は次のとおりです。
1 | OpenCL says: 'kernel arguments are `b`, 2.000000, 3' |
バインドにより input_port<0> のみが使用されるため、2 つ目と 3 つ目のポートに渡されたメッセージは無視されます。ただし、opencl_node では、メッセージを実行するためにはすべての input_port にメッセージを送る必要があります。不要なポートは、次のように opencl_node の宣言で省略することができます。
1 2 3 4 5 | 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 は範囲に含まれます)。
1 2 3 4 5 | 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(); |
出力は次のとおりです。
1 | OpenCL says: 'kernel arguments are `c`, 4.000000, 5' |
opencl_node 実行ごとに ndrange を指定する場合、set_ndranges とともに port_ref を使用することもできます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | #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 が入力ポートにバインドされている場合は、明示的に引数をバインドする必要があります。
このシリーズの次の記事では、順序付けの問題を回避するため、型指定したメッセージキーの使用法について述べます。
コンパイラーの最適化に関する詳細は、最適化に関する注意事項を参照してください