本文へジャンプ

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む


お客様が developerWorks に初めてサインインすると、プロフィールが作成されます。プロフィールで選択した情報は公開されますが、いつでもその情報を編集できます。お客様の姓名(非表示設定にしていない限り)とディスプレイ・ネームは、投稿するコンテンツと一緒に表示されます。

送信されたすべての情報は安全です。

  • 閉じる [x]

developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む


送信されたすべての情報は安全です。

  • 閉じる [x]

Let's LTS: 第 3 回 タスクコンテキストとタスクパイプライン

タスクコンテキストを利用して SPE に起動パラメータを渡す方法と、タスク依存関係を使ってパイプライン実行を行う方法について解説します

土居 意弘 (munepi@jp.ibm.com), IBM Systems & Technology Group, AKD48
土居 意弘
土居意弘はIBMで5年間、Cell/B.E. や Digital Signal Processor 上で画像処理システムなどを開発してきました。現在は日本IBMのテクニカルエキスパートとして、 Cell/B.E. 向けソフトウェアの実装や Hybrid & Multicore システム設計のコンサルティング業務などを行っています。

概要: Accelerated Library Framework (ALF) の新しいタスクモデルである、軽量タスクサポート (Lightweight task support, LTS) が SDK 3.1 から加わりました。本記事では、タスクコンテキストを使って SPE へ起動時パラメータを渡す方法、タスク依存関係を使ってパイプライン実行を行う方法について解説します。

このシリーズの他の記事を見る

日付:  2009年 4月 17日
レベル:  中級
アクティビティー: 3518 ビュー
お気軽にご意見・ご感想をお寄せください: 


はじめに

アクセラレータ (SPE) にタスクを実行させる場合には、パラメータを渡して作業内容を指示してやらなければなりません。
ALF LTS ではタスクコンテキストを使ってアクセラレータへ起動時パラメータを渡すことができます。本記事では、このタスクコンテキストの使い方を解説します。

タスクコンテキストは ALF LTS 専用の機能ではなく、従来の ALF (ワークブロックタスク)でもリダクションやアクセラレータデータ分割など、分割統治型のアルゴリズムを実装する際に活用されている機能です。
また、分割統治型のアルゴリズムでは、前後のタスクの依存関係を正しく順序付けてやらなくてはなりません。

タスクコンテキストと依存関係の考え方を学んでおくことは、ALF 以外のマルチコアフレームワークを使って並列処理を行う場合にも役立つことでしょう。


タスクコンテキストとは

タスクコンテキストとは、タスクに対して割り当てられるデータセットのことです。
タスクコンテキストを用意しておくと、その内容が、タスクの実行直前にアクセラレータのローカルメモリにコピーされます(図 1 )。

例えば、タスクコンテキストにシステムメモリ上のアドレスを格納しておき、アクセラレータはそのアドレスに対して DMA 転送を行ってデータを取得し、それを処理する、といった用途に使用できます。


図 1. タスクコンテキスト
図 1. タスクコンテキスト

まずは使ってみよう

あるデータ列の最大値・最小値を探すプログラムを、タスクコンテキストを使用して作成してみましょう。
仕様は以下のとおりです(図 2 )。

  • バラバラに並んだシステムメモリ上のデータ列から、アクセラレータで最大値・最小値を探索する。
  • タスクコンテキストを使用してデータ列のアドレスをアクセラレータへ渡す。
  • 発見した最大値・最小値は、データ列の先頭に DMA 転送で書き込む。

図 2. 最大値・最小値の探索
図 2. 最大値・最小値の探索

リスト 1 がホスト側 (PPE) のプログラムです。


リスト 1. 最大値・最小値探索プログラム (ホスト側)

001: #include <stdio.h>   // for NULL, printf()
002: #include <memory.h>  // for memcpy
003: #include <alf.h>     // for alf_xxxx()
004: 
005: #include "task_context.h"
006: 
007: #define NUM_SPE  (1)
008: 
009: uint32_t  _data[32] __attribute__((aligned(128))) = {
010:          1, 15, 28, 14,
011:         25, 20, 19, 11,
012:         21, 31,  2, 13,
013:          6,  5, 17, 27,
014:         18, 10, 30, 23,
015:          8,  7,  9, 26,
016:         29, 22, 12,  0,
017:          4, 16, 24,  3
018: };
019: 
020: int main(int argc, const char* argv[])
021: {
022:     task_context_t  ctx;
023:     alf_handle_t handle;
024:     alf_task_desc_handle_t task_desc_handle;
025:     alf_task_handle_t task_handle;
026:     
027:     //-------------------------------------------
028:     // Initialization
029:     //-------------------------------------------
030:     // Set parameter to be passed to SPEs.
031:     ctx.addr   = (uint64_t)((uint32_t)_data);
032:     ctx.length = sizeof(_data) / sizeof(_data[0]);
033:     ctx.numspe = NUM_SPE;
034:     
035:     // Initialize ALF.
036:     alf_init(NULL, &handle);
037:     alf_num_instances_set(handle, NUM_SPE);
038:     
039:     //-------------------------------------------
040:     // Finding phase
041:     //-------------------------------------------
042:     // Create task descriptor.
043:     alf_task_desc_create(handle,
044:             ALF_ACCEL_TYPE_EDP,
045:             &task_desc_handle);
046: 
047:     // Set parameters for task main function.
048:     alf_task_desc_set_int32(task_desc_handle,
049:             ALF_TASK_DESC_TASK_TYPE,
050:             ALF_TASK_TYPE_LIGHTWEIGHT);
051:     alf_task_desc_set_int64(task_desc_handle,
052:             ALF_TASK_DESC_ACCEL_LIBRARY_REF_L,
053:             (unsigned long long)"libspu_library.so");
054:     alf_task_desc_set_int64(task_desc_handle,
055:             ALF_TASK_DESC_ACCEL_IMAGE_REF_L,
056:             (unsigned long long)"spu_prog");
057:     alf_task_desc_set_int64(task_desc_handle,
058:             ALF_TASK_DESC_ACCEL_LTS_MAIN_REF_L,
059:             (unsigned long long)"task_main");
060:     
061:     // Set parameter for task context.
062:     alf_task_desc_set_int32(task_desc_handle,
063:             ALF_TASK_DESC_MAX_STACK_SIZE,
064:             4096);
065:     alf_task_desc_set_int32(task_desc_handle,
066:             ALF_TASK_DESC_TSK_CTX_SIZE,
067:             sizeof(task_context_t));
068:     alf_task_desc_ctx_entry_add(task_desc_handle,
069:             ALF_DATA_BYTE,
070:             sizeof(task_context_t));
071:     
072:     // Run the task and wait.
073:     alf_task_create(task_desc_handle, &ctx, NUM_SPE, 0, 0, &task_handle);
074:     alf_task_finalize(task_handle);
075:     alf_task_wait(task_handle, -1);
076:     
077:     //-------------------------------------------
078:     // Cleaning up
079:     //-------------------------------------------
080:     // Exit ALF.
081:     alf_exit(handle, ALF_EXIT_POLICY_FORCE, 0);
082: 
083:     // Output result.
084:     printf("Maximum=%u, Minimum=%u\n", _data[0], _data[1]);
085:     
086:     return 0;
087: }

  • 9~18行目: 探索対象のデータ列です。0~31 までの値がバラバラに並んでいます。
  • 30~33行目: タスクコンテキストとして渡すデータを初期化しています。このサンプルプログラムでは、task_context_t という構造体でタスクコンテキストを渡します。内容はデータ列のアドレス、データ総数、探索に使ったアクセラレータの個数です。
  • 61~70行目: タスクコンテキストを使うための設定です。
    • ALF_TASK_DESC_MAX_STACK_SIZE はランタイムが使用する作業用メモリのサイズです。何も指定しなければ 1024 が使われます。タスクコンテキストのサイズ以上であれば構いません。
    • ALF_TASK_DESC_TSK_CTX_SIZE はタスクコンテキストに使われるメモリのサイズです。デフォルトでは 0 ですので、タスクコンテキストを使用する場合は必ずセットする必要があります。
    • alf_task_desc_ctx_entry_add() は、タスクコンテキストの内容を宣言します。この例ではバイト配列として宣言しています。
  • 73行目: タスクコンテキストとして渡すデータを引数として与えます。

リスト 2 はアクセラレータ側 (SPE) のプログラムです。


リスト 2. 最大値・最小値探索プログラム (アクセラレータ側)

001: #include <spu_mfcio.h>
002: #include <stdio.h>
003: 
004: #include <alf_accel.h>
005: 
006: #include "../task_context.h"
007: 
008: uint32_t _data[32] __attribute__((aligned(128)));
009: 
010: int task_main(void* p_task_ctx ,
011:         int instance_id,
012:         int number_of_instance)
013: {
014:     unsigned int i, length;
015:     uint64_t addr;
016:     uint32_t maximum, minimum;
017:     task_context_t* ctx = (task_context_t*)p_task_ctx;
018: 
019:     // Get data
020:     length  = ctx->length;
021:     addr = ctx->addr;
022:     mfc_get(_data,
023:             addr,
024:             sizeof(uint32_t) * length,
025:             0, 0, 0);
026:     mfc_write_tag_mask(1 << 0);
027:     mfc_read_tag_status_all();
028: 
029:     // Find maximum and minimum.
030:     maximum = 0;
031:     minimum = 0x7fffffff;
032:     for (i = 0; i < length; i++)
033:     {
034:         if ( _data[i] > maximum ) maximum = _data[i];
035:         if ( _data[i] < minimum ) minimum = _data[i];
036:     }
037:     printf("[%d/%d] Maximum=%u, Minimum=%u\n",
038:             instance_id,
039:             number_of_instance,
040:             maximum, minimum);
041: 
042:     // Put result
043:     _data[0] = maximum;
044:     _data[1] = minimum;
045:     mfc_put(_data,
046:             addr,
047:             sizeof(uint32_t) * 2,
048:             0, 0, 0);
049:     mfc_write_tag_mask(1 << 0);
050:     mfc_read_tag_status_all();
051:     
052:     return 0;
053: }
054: 
055: ALF_ACCEL_EXPORT_API_LIST_BEGIN;
056: ALF_ACCEL_EXPORT_API("", task_main);
057: ALF_ACCEL_EXPORT_API_LIST_END;

  • 10, 17行目: タスクメイン関数の第一引数としてタスクコンテキストが渡されます。このサンプルプログラムでは task_context_t という構造体でデータを渡しているので、キャストして使います。
  • 19~27行目: 探索対象のデータ列をローカルメモリに転送します。
  • 29~36行目: 最大値、最小値の探索をしています。
  • 42~50行目: 発見した最大値・最小値をシステムメモリに転送します。

リスト 3 では、ホストとアクセラレータでタスクコンテキストとして共有されるデータ構造を宣言しています。


リスト 3. タスクコンテキストのデータ構造

001: #ifndef TASK_CONTEXT__H__
002: #define TASK_CONTEXT__H__
003: 
004: #include <stdint.h>
005: 
006: typedef struct task_context_tag
007: {
008:     uint64_t addr;
009:     uint32_t length;
010:     uint32_t numspe;
011:     
012: } task_context_t;
013: 
014: #endif//TASK_CONTEXT__H__

実行すると図 3 のような結果になります。


図 3. 実行結果
図 3. 実行結果

複数のアクセラレータがある場合

マルチコアを活用するためには、アルゴリズムを並列化する必要があります。各アクセラレータで部分ごとに処理させて、その結果を統合するアルゴリズム(分割統治)に変更してみましょう(図 4 )。
部分ごとの処理はこれまでのプログラムを活用し、統合するプログラムを新たに追加します。


図 4. 分割統治型の最大値・最小値探索
図 4. 分割統治型の最大値・最小値探索

複数のアクセラレータを使用する場合でも、タスクコンテキストは全てのアクセラレータで共有されます(図 5 )。アクセラレータごとに処理する部分を変えるためには、タスクメイン関数に渡される instance_idnumber_of_instance を活用します。


図 5. 複数のアクセラレータがある場合のタスクコンテキスト
図 5. 複数のアクセラレータがある場合のタスクコンテキスト

分割統治型のホスト側プログラムをリスト 4 に示します。変更のない部分は省略してあります。


リスト 4. 複数のアクセラレータがある場合の最大値・最小値探索プログラム (ホスト側、差分のみ)

・・・
007: #define NUM_SPE  (4)
・・・
077:     //-------------------------------------------
078:     // Reduction phase
079:     //-------------------------------------------
080:     // Set parameters for task main function.
081:     alf_task_desc_set_int64(task_desc_handle,
082:             ALF_TASK_DESC_ACCEL_LTS_MAIN_REF_L,
083:             (unsigned long long)"reduction_main");
084:     
085:     // Run the task and wait.
086:     alf_task_create(task_desc_handle, &ctx, NUM_SPE, 0, 0, &task_handle);
087:     alf_task_finalize(task_handle);
088:     alf_task_wait(task_handle, -1);
・・・

  • 7行目: アクセラレータの個数を増やします。この例題では 2 の累乗のみサポートしています。
  • 77目~88行目: 統合処理をするタスクを追加します。reduction_main という名前の統合用タスクメイン関数を使用します。

アクセラレータ側では、統合処理のためのタスクメイン関数 reduction_main() を追加しています。また、最大値・最小値探索のためのタスクメイン関数 task_main() も少しだけ変更があります(リスト 5 )。


リスト 5. 複数のアクセラレータがある場合の最大値・最小値探索プログラム (アクセラレータ側、差分のみ)

・・・
020:     length  = ctx->length / number_of_instance;
021:     addr = ctx->addr + sizeof(uint32_t) * length * instance_id;
・・・
055: int reduction_main(void* p_task_ctx ,
056:         int instance_id,
057:         int number_of_instance)
058: {
059:     unsigned int i, length;
060:     uint32_t maximum, minimum;
061:     task_context_t* ctx = (task_context_t*)p_task_ctx;
062: 
063:     maximum = 0;
064:     minimum = 0x7fffffff;
065:     length = ctx->length / ctx->numspe;
066:     for (i = 0; i < ctx->numspe; i++)
067:     {
068:         // Get data
069:         mfc_get(_data, 
070:                 ctx->addr + i * sizeof(uint32_t) * length,
071:                 sizeof(uint32_t) * 2,
072:                 0, 0, 0);
073:         mfc_write_tag_mask(1 << 0);
074:         mfc_read_tag_status_all();
075:         
076:         // Find maximum/minimum of partial results.
077:         if ( _data[0] > maximum ) maximum = _data[0];
078:         if ( _data[1] < minimum ) minimum = _data[1];
079:     }
080:     
081:     // Put result
082:     _data[0] = maximum;
083:     _data[1] = minimum;
084:     mfc_put(_data,
085:             ctx->addr,
086:             sizeof(uint32_t) * 2,
087:             0, 0, 0);
088:     mfc_write_tag_mask(1 << 0);
089:     mfc_read_tag_status_all();
090:     
091:     return 0;
092: }
093: 
094: ALF_ACCEL_EXPORT_API_LIST_BEGIN;
095: ALF_ACCEL_EXPORT_API("", task_main);
096: ALF_ACCEL_EXPORT_API("", reduction_main);
097: ALF_ACCEL_EXPORT_API_LIST_END;

  • 20行目: 探索対象データの総数をアクセラレータの個数で割り、自分が担当すべき個数を求めています。
  • 21行目: 同様に、自分が担当すべき範囲の、先頭アドレスを求めています。
  • 55~92行目: 統合のためのタスクメイン関数です。図 4 の黄色で示した部分のみを順に読み取り、最大値・最小値を求めます。
  • 96行目: エクスポートも忘れずに。

これを実行すると図 4 のような結果になります。順番は環境によって異なるでしょう。


図 6. 複数アクセラレータの場合の実行結果
図 6.  複数アクセラレータの場合の実行結果

各アクセラレータで異なる部分を並列処理することができました。


タスクパイプライン

リスト 4 では、最大値・最小値の部分探索と統合を2つのタスクに分けて、探索の完了を待機してから統合を実行していました。しかし、ALF にはタスク依存関係を定義することでこの待機&実行を自動的に行う仕組みがあります。

この仕組みを使ってタスクをパイプライン実行させるようにしたのが、リスト 6 です。


リスト 6. タスクパイプライン (ホスト側)

・・・
020: int main(int argc, const char* argv[])
021: {
・・・
025:     alf_task_handle_t task_handle_1;
026:     alf_task_handle_t task_handle_2;
027:     
・・・(Initializationは同じ)
040:     //-------------------------------------------
041:     // Finding phase
042:     //-------------------------------------------
・・・
073:     // Create task
074:     alf_task_create(task_desc_handle, &ctx, NUM_SPE, 0, 0, &task_handle_1);
075:     
076:     //-------------------------------------------
077:     // Reduction phase
078:     //-------------------------------------------
・・・
084:     // Create task
085:     alf_task_create(task_desc_handle, &ctx, NUM_SPE, 0, 0, &task_handle_2);
086: 
087:     //-------------------------------------------
088:     // Execution with pipelining
089:     //-------------------------------------------
090:     alf_task_depends_on(task_handle_2, task_handle_1);
091:     
092:     alf_task_finalize(task_handle_1);
093:     alf_task_finalize(task_handle_2);
094:     alf_task_wait(task_handle_2, -1);
095:     
096:     //-------------------------------------------
097:     // Cleaning up
098:     //-------------------------------------------
・・・

  • 25~26行目: タスクハンドルは 2 つ用意しなければなりません。これまでは前のタスクが終ってから次のタスクを作成していたので再利用できましたが、パイプラインの場合にはできません。
  • 74、85行目: 各タスクは作成だけを行い、ファイナライズと完了待機は後で行います。ファイナライズされるまで実行は始まりません。
  • 90行目: 作成済みの 2 つのタスクの間に依存関係を設定しています。1 番目の引数で与えたタスクは、2 番目の引数で与えたタスクが完了するまで待機してから、実行を開始します。
  • 92~93行目: 依存関係を定義したらファイナライズを行います。ファイナライズされると実行が始まります。
  • 94行目: 2 番目のタスクを待機します。1 番目のタスクの完了を待機する必要はありません。

実際には、alf_task_depends_on() の呼び出しは、1 番目のタスクのファイナライズ後でも構いません(そしてもちろん 2 番目のタスクのファイナライズ前でなければなりません)。ですので、forループの中で依存関係のチェーンを作りながら次々とタスクを実行するようなことも可能です。


タスクコンテキストを戻り値に使えるか

例ではシステムメモリにデータを置いて DMA 転送で読み書きを行いましたが、タスクコンテキストに直接データ列を与えて書き換えることはできないのでしょうか?

答えは No です。
LTS ではタスクコンテキストに対する書き込みはサポートされていません。書き込みをしてもホスト側に反映されません。結果を戻す場合は、本記事の例題のようにタスクコンテキストの中に書き込み先のアドレスを格納しておいて明示的に DMA 転送を実行する必要があります。


Tips: C++ で使う場合

ところで、皆さんの中には普段は C++ を使っているという方も多いと思います。

もちろん、ALF LTS も C++ とともに使うことができますが、少しだけ工夫が必要ですのでここで紹介しておきます。

タスクメイン関数のエクスポートは、 3 つのマクロ ALF_ACCEL_EXPORT_API_LIST_BEGINALF_ACCEL_EXPORT_APIALF_ACCEL_EXPORT_API_LIST_END を利用します。
しかし、マクロ内部でライブラリの外部参照を利用しており、ここが C++ に対応していません。

対策をしたものがリスト 7 です。


リスト 7. C++ で ALF を使う場合 (アクセラレータ側)

001: #include <alf_accel.h>
002: 
003: extern "C"
004: int task_main(void* p_task_ctx ,
005:         int instance_id,
006:         int number_of_instance)
007: {
008:     return 0;
009: }
010: 
011: extern "C"
012: {
013: ALF_ACCEL_EXPORT_API_LIST_BEGIN;
014: ALF_ACCEL_EXPORT_API("", task_main);
015: ALF_ACCEL_EXPORT_API_LIST_END;
016: }

まず ALF_ACCEL_EXPORT_API_LIST_BEGINALF_ACCEL_EXPORT_API_LIST_END の前後を extern "C" { ... } で囲みます (11 行目~ 16 行目) 。これにより、マクロの問題を回避できます。

しかし、この副作用として 14 行目でエクスポートする関数名に、 C++ の装飾名が使えなくなります (注) 。そこで、タスクメイン関数のプロトタイプ宣言および定義にも extern "C" を付けます。

以上のことに注意すれば C++ でも ALF LTS を使うことができます。

ホスト側は C++ に対応しているので、特に注意事項はありません。

(注) C++ では、関数オーバーロード機能への対応のために関数名を内部的に変更(装飾)しています。しかし、この関数名装飾の方法はコンパイラ依存であり、異なるコンパイラ間や異なる言語間で共通に使用されるライブラリを作成する場合には、 extern "C" を付加して装飾名の使用を禁止するのが一般的な方法となっています。装飾を禁止した関数については、オーバーロード機能は使用できなくなります。


まとめ

本記事では、 ALF 軽量タスクサポートを使って SPE タスクに起動時パラメータ(コンテキスト)を渡す方法と、依存関係を利用したタスクパイプラインについて解説しました。


参考文献

学ぶために

製品や技術を入手するために

議論するために

著者について

土居 意弘

土居意弘はIBMで5年間、Cell/B.E. や Digital Signal Processor 上で画像処理システムなどを開発してきました。現在は日本IBMのテクニカルエキスパートとして、 Cell/B.E. 向けソフトウェアの実装や Hybrid & Multicore システム設計のコンサルティング業務などを行っています。

不正使用の報告のヘルプ

不正使用の報告

ありがとうございます。 このエントリーは、モデレーターの注目フラグが設定されました。


不正使用の報告のヘルプ

不正使用の報告

不正使用の報告の送信に失敗しました。


developerWorks: サイン・イン


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 利用条件

 


お客様が developerWorks に初めてサインインすると、プロフィールが作成されます。 プロフィールで選択した情報は公開されますが、いつでもその情報を編集できます。 お客様の姓名(非表示設定にしていない限り)とディスプレイ・ネームは、投稿するコンテンツと一緒に表示されます。

表示名をお選びください

developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

(半角英数字で3文字以上31文字以下にする必要があります)


「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 利用条件

 


この記事を評価する

コメント

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Multicore acceleration
ArticleID=381257
ArticleTitle=Let's LTS: 第 3 回 タスクコンテキストとタスクパイプライン
publish-date=04172009
author1-email=munepi@jp.ibm.com
author1-email-cc=

タグ

Help
このタグで、My developerWorks のすべてのタイプのコンテンツを見つけるために検索フィールドを使用します。

スライダーバーを使用することで、より多く(少なく)タグを表示します。

人気のタグは、この特定のコンテンツ・ゾーン(例えば、Java テクノロジー、Linux や WebSphere など)に対するトップのタグを表示します。

マイ・タグは、この特定のコンテンツ・ゾーン(例えば、Java テクノロジー、Linux や WebSphere など)に対するお客様ご自身のタグを表示します。

このタグで、My developerWorks のすべてのタイプのコンテンツを見つけるために検索フィールドを使用します。人気のタグは、この特定のコンテンツ・ゾーン(例えば、Java テクノロジー、Linux や WebSphere など)に対するトップのタグを表示します。マイ・タグは、この特定のコンテンツ・ゾーン(例えば、Java テクノロジー、Linux や WebSphere など)に対するお客様ご自身のタグを表示します。