この記事は、インテル® デベロッパー・ゾーンに公開されている「opencl_node overview」(https://software.intel.com/en-us/blogs/2015/12/09/opencl-node-overview/) の日本語参考訳です。
はじめに
インテル® スレッディング・ビルディング・ブロック (インテル® TBB) ライブラリーは、C++ アプリケーションで並列処理を可能にするアルゴリズムのセットを提供します。インテル® TBB 4.0 から、flow graph クラスおよび関数を利用して、非構造化並列処理、依存性グラフ、データフロー・アルゴリズムを表現できるようになりました。フローグラフにより、下位レベルのタスク API を使用することなく、インテル® TBB の汎用並列アルゴリズムでカバーされないケースに対応できます。
システムはますますヘテロジニアス化し、CPU の処理能力だけでなく、特定のタスクに適したさまざまなアクセラレーターを備えるようになってきています。この変化に対応し、ヘテロジニアス・システム向けに効率良くプログラムするため、インテル® TBB 4.4 ではフローグラフ・インターフェイスが拡張され、フローグラフがユーザーまたは別のランタイムにより管理される外部処理と効率良く通信できるようにする async_node が追加されました。さらに、インテル® TBB 4.4 Update 2 では、フローグラフで OpenCL* デバイスの利用と連携を可能にする opencl_node も追加されました。現在、async_node と opencl_node はどちらもプレビュー機能として提供されています。
このシリーズでは、opencl_node の機能について詳しく説明します。第一弾となるこの記事では、簡単な “Hello, World!” サンプルについて見てみましょう。その後、opencl_node の基本インターフェイスを示し、メモリー・オブジェクトの使用法、特定の OpenCL* デバイスの選択、インターフェイスの詳細について説明していきます。
“Hello world”
以下の “Hello, World!” サンプルは、opencl_node の使用法を示します。
hello_world.cpp:
#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_node<tuple<opencl_buffer<cl_char>>> clPrint( g, "hello_world.cl", "print" ); const char str[] = "Hello, World!"; opencl_buffer<cl_char> b( g, sizeof(str) ); std::copy_n( str, sizeof(str), b.begin() ); clPrint.set_ndranges( { 1 } ); input_port<0>(clPrint).try_put( b ); g.wait_for_all(); return 0; }
hello_world.cl:
kernel void print( global char *str ) { printf("OpenCL says '"); for ( ; *str; ++str ) printf("%c", *str); printf("'\n"); }
Microsoft* Windows* 上でこのサンプルをコンパイルするには、Microsoft* Visual Studio* 2013 の Visual C++* コンパイラーまたはインテル® C++ コンパイラー 15.0 以降が必要です。
Microsoft* Visual C++* コンパイラーでコンパイルする場合は、次のコマンドを実行します。
>cl /EHsc hello_world.cpp /wd4503 /link OpenCL.lib
インテル® C++ コンパイラーでコンパイルする場合は、次のコマンドを実行します。
>icl /Qstd=c++11 hello_world.cpp /link OpenCL.lib
サンプルのコンパイルには、インテル® SDK for OpenCL* Applications が必要です。
また、C++11 をサポートする任意の C++ コンパイラーを使用して、Windows* およびその他のオペレーティング・システム (Linux* や OS X* など) でコンパイルすることもできます。
実行後、次の出力が表示されるはずです。
>hello_world.exe OpenCL says 'Hello, World!'
期待どおり、サンプルは “Hello, World!” メッセージを出力しました。
コードについて詳しく見てみましょう。
opencl_node を使用するには、特定のヘッダーファイル (適切なプレビューマクロの定義を含む) をインクルードする必要があります。
#define TBB_PREVIEW_FLOW_GRAPH_NODES 1 #include "tbb/flow_graph_opencl_node.h"
ほかのフローグラフ・ノードと違い、opencl_node は opencl_graph オブジェクトでのみ作成することができます。
opencl_graph g; opencl_node<tuple<opencl_buffer<cl_char>>> clPrint( g, "hello_world.cl", "print" );
ただし、opencl_graph オブジェクトは、opencl_node オブジェクトだけではなく、ほかのフローグラフ・ノードの作成にも使用できます。opencl_node がプレビュー機能である間は、この特定のグラフを使用する必要があります。この制限は、将来解除される予定です。
このサンプルで、opencl_node は tuple<opencl_buffer<cl_char>> でインスタンス化され、opencl_buffer<cl_char> 型の 1 つの入力ポートと 1 つの出力ポートを持つノードを作成します。第 2 引数には OpenCL* プログラムを、第 3 引数にはプログラムから取得するカーネルを指定します。
OpenCL* カーネルは、ホストで作成されたメモリー・オブジェクトを処理できます。シンボルの線形配列を opencl_buffer<cl_char> で表し、C 形式の文字列からシンボルを格納します。
const char str[] = "Hello, World!"; op encl_buffer<cl_char> b( g, sizeof(str) ); std::copy_n( str, sizeof(str), b.begin() );
OpenCL* カーネル呼び出しには、実行の反復空間 (ndrange) が必要です。このサンプルでは、set_ndranges メソッドを使用して opencl_node の ndrange を設定しています。
clPrint.set_ndranges( { 1 } );
ここでは、サイズ 1 の 1 次元の範囲がカーネルに渡されます。
カーネルの実行には、通常のフローグラフ・インターフェイス try_put を使用します。
input_port<0>(clPrint).try_put( b );
opencl_node には 1 つの入力ポートと 1 つの出力ポートしかありませんが、このサンプルでは複数入力と複数出力に対応しなければならないため、input_port および output_port ヘルパー関数が使用されます。
try_put は、非同期メソッドです。実行を初期化しますが、処理の完了を待機しません。待機する場合は、wait_for_all メソッドを呼び出します。
g.wait_for_all();
メインスレッドが wait_for_all からリターンすると、カーネルが処理を完了し、標準出力に “Hello, World!” が送られたことが保証されます。
今後予定されている記事
opencl_node の基本インターフェイスと opencl_buffer
デバイスの選択
opencl_program と引数のバインド
順序付けの問題
まとめ
このシリーズでは、インテル® TBB フローグラフ内で opencl_node を使用するための基本原理を示します。この記事では概要のみ説明しました。一部の機能については触れるだけで、詳しく説明しませんでした。また、意図的に簡単なサンプルを使用しました。この記事が opencl_node の機能を学ぶ際の取り掛かりになればと思います。
opencl_node はプレビュー機能として提供されており、変更される可能性 (API や動作の互換性に影響する変更を含む) があることに注意してください。
この記事や opencl_node に関するご意見・ご感想がありましたら、コメントを残してください。
コンパイラーの最適化に関する詳細は、最適化に関する注意事項を参照してください