opencl_program と引数のバインド – パート 4

同カテゴリーの次の記事

順序付けの問題 – パート 5

この記事は、インテル® デベロッパー・ゾーンに公開されている「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 が入力ポートにバインドされている場合は、明示的に引数をバインドする必要があります。

このシリーズの次の記事では、順序付けの問題を回避するため、型指定したメッセージキーの使用法について述べます。

コンパイラーの最適化に関する詳細は、最適化に関する注意事項を参照してください

関連記事

  • デバイスの選択 – パート 3デバイスの選択 – パート 3 この記事は、インテル® デベロッパー・ゾーンに公開されている「Device selection」の日本語参考訳です。 この記事は、インテル® スレッディング・ビルディング・ブロック (インテル® TBB) 4.4 Update 2 以降で利用可能な新しいノード opencl_node について説明するシリーズのパート 3 […]
  • 順序付けの問題 – パート 5順序付けの問題 – パート 5 この記事は、インテル® デベロッパー・ゾーンに公開されている「Ordering issues」の日本語参考訳です。 この記事の PDF 版はこちらからご利用になれます。 この記事は、インテル® スレッディング・ビルディング・ブロック (インテル® TBB) 4.4 Update 2 以降で利用可能な新しいノード […]
  • opencl_node の基本インターフェイスと opencl_buffer – パート 2opencl_node の基本インターフェイスと opencl_buffer – パート 2 この記事は、インテル® デベロッパー・ゾーンに公開されている「opencl_node basic interfaces and opencl_buffer」の日本語参考訳です。 この記事は、インテル® スレッディング・ビルディング・ブロック (インテル® TBB) 4.4 Update 2 以降で利用可能な新しいノード […]
  • opencl_node の概要 – パート 1opencl_node の概要 – パート 1 この記事は、インテル® デベロッパー・ゾーンに公開されている「opencl_node overview」の日本語参考訳です。 はじめに インテル® スレッディング・ビルディング・ブロック (インテル® TBB) ライブラリーは、C++ アプリケーションで並列処理を可能にするアルゴリズムのセットを提供します。インテル® […]
  • GEN アセンブリーの概要GEN アセンブリーの概要 この記事は、インテル® デベロッパー・ゾーンに公開されている「Introduction to GEN Assembly」の日本語参考訳です。 この記事の PDF 版はこちらからご利用になれます。 目次 はじめに 単純な OpenCL* カーネルのアセンブリー アセンブリー命令の読み方 参考文献 […]