 | レベル: 初級 Kane Scarlett, Editor, Multicore acceleration,
IBM
2008年 3月 18日 今回の Cell Broadband Engine(TM) (Cell/B.E.) シリーズでは、IBM SDK for Multicore Acceleration 3.0に含まれるAccelerated Library Framework (ALF)で2つの大きな行列を加算する方法を2つ学びます。 1つはホスト分割による例、1つはアクセラレータ分割による例です。 "ALF for Cell/B.E.
Programmer's Guide and API Reference, Version 3.0" (参考資料を参照) を元に構成しています。
はじめに
本稿では、ALFを使って2つの大きな行列の和を計算します。問題は簡潔にこう表現できます。
 |
もっと Fun with ALF
もっとFun with ALF シリーズを探検しましょう:
- "テーブル参照 (Table lookup)"で、16ビットの入力データを8ビットの出力バッファに変換するために、大きなルックアップテーブルとしてタスクコンテキストバッファをどのように使うのかを学びましょう。
- "Min-max 探索 (Min-max finder)"では、タスクコンテキストを使って、各タスクインスタンスの部分の計算結果を保持し、これらの部分結果を併せて最終結果を得る方法を学びます。.
- "複数のベクトルの内積 (Multiple vector dot products)"では、タスクコンテキストとバンドルワークブロックを使って、ローカルメモリのサイズ制限のためにワークブロックが分割されたデータを保持できない状況への対処法を明らかにします。
- "オーバーラップI/Oバッファ (Overlapped I/O buffer)"では、オーバーラップI/Oバッファを行列の加算に使用する方法について調べています。
- "タスク依存関係 (Task dependency)"では、2つのステージを持つパイプラインアプリケーションを題材にタスクの依存関係を使う簡単なシミュレーションを行います。
そして、DaCS, BLASそしてその他の技術に注目したFun withシリーズも見てみましょう。あなたのCell/B.E.プログラミングを簡単にしてくれますよ。
|
|
A[m,n] + B[m,n] = C[m,n]
ここで、mとnは行列の各次元です。
ホストデータ分割およびアクセラレータデータ分割のどちらも考慮したこの単純な例を用いて、以下の方法について説明します:
- ALFランタイム環境を開始する。
- タスクデスクリプタを使う。
- アクセラレータ上でタスクを開始する。
- ワークブロックを作成しタスクに追加する。
- ALFランタイム環境を正しく終了する。
この例をひな形として、もっと複雑なアプリケーションを作ることができるでしょう。
ホストデータ分割の例について見てみよう
この例では、ホストアプリケーションは以下のことを行います:
- ALFランタイム環境を初期化する。
- タスクデスクリプタを作成する。
- そのタスクデスクリプタに基づいたタスクを作成する。
- アクセラレータ上の計算カーネルの実行を行うための適切なデータ転送リストを考えて、ワークブロックを作成する。
- 計算カーネルの完了を待機し、終了する。
アクセラレータアプリケーションには、2つの行列の和を計算するシンプルな計算カーネルを実装します。単一プロセッサのコンピュータで2つの行列の和を計算するスカラーコードは次のようになります。
float mat_a[NUM_ROW][NUM_COL];
float mat_b[NUM_ROW][NUM_COL];
float mat_c[NUM_ROW][NUM_COL];
int main(void)
{
int i,j;
for (i=0; i<NUM_ROW; i++)
for (j=0; j<NUM_COL; j++)
mat_c[i][j] = mat_a[i][j] + mat_b[i][j];
return 0;
}
|
ホストデータ分割のソースコードを見てみよう
以降のコードリストは説明に関連のある部分だけを示します。リスト全体を見るには、ALFのサンプルディレクトリmatrix_add/STEP1a_partition_scheme_A/common/host_partitionを参照してください。
ALFのホストプログラムは以下のセクションに分けて考えられます。
- 初期化
- タスクのセットアップ
- ワークブロックのセットアップ
- タスクの待機と終了
初期化
以下のコードは、どのようにALFを初期化するのか、そしてどのようにアクセラレータを特定のALFランタイムにアロケートするかを示しています。
alf_handle_t alf_handle;
unsigned int nodes;
/* initializes the runtime environment for ALF */
alf_init(&config_parms, &alf_handle);
/* get the number of SPE accelerators available for from the Opteron */
rc = alf_query_system_info(alf_handle, ALF_QUERY_NUM_ACCEL, ALF_ACCEL_TYPE_SPE, &nodes);
/* set the total number of accelerator instances (in this case, SPE) */
/* the ALF runtime will have during its lifetime */
rc = alf_num_instances_set (alf_handle, nodes);
|
タスクのセットアップ
ALFホストプログラムのこのセクションでは、タスクの設定とタスクランタイムの作成を行っています。
alf_task_desc_create 関数はタスクデスクリプタを作成します。このデスクリプタを使用して異なる実行可能タスクを複数作ることもできます。alf_task_create関数でspe_add_programという名前のSPEプログラムを実行するタスクを作成しています。
/* variable declarations */
alf_task_desc_handle_t task_desc_handle;
alf_task_handle_t task_handle;
const char* spe_image_name;
const char* library_path_name;
const char* comp_kernel_name;
/* describing a task that's executable on the SPE*/
alf_task_desc_create(alf_handle, ALF_ACCEL_TYPE_SPE, &task_desc_handle);
alf_task_desc_set_int32(task_desc_handle, ALF_TASK_DESC_TSK_CTX_SIZE, 0);
alf_task_desc_set_int32(task_desc_handle, ALF_TASK_DESC_WB_PARM_CTX_BUF_SIZE,
sizeof(add_parms_t));
alf_task_desc_set_int32(task_desc_handle, ALF_TASK_DESC_WB_IN_BUF_SIZE,
H * V * 2 sizeof(float));
alf_task_desc_set_int32(task_desc_handle, ALF_TASK_DESC_WB_OUT_BUF_SIZE,
H * V * sizeof(float));
alf_task_desc_set_int32(task_desc_handle, ALF_TASK_DESC_NUM_DTL_ENTRIES, 8);
alf_task_desc_set_int32(task_desc_handle, ALF_TASK_DESC_MAX_STACK_SIZE, 4096);
/* providing the SPE executable name */
alf_task_desc_set_int64(task_desc_handle,
ALF_TASK_DESC_ACCEL_IMAGE_REF_L,(unsigned long long) spe_image_name);
alf_task_desc_set_int64(task_desc_handle,
ALF_TASK_DESC_ACCEL_LIBRARY_REF_L,(unsigned long) library_path_name);
alf_task_desc_set_int64(task_desc_handle,
ALF_TASK_DESC_ACCEL_KERNEL_REF_L,(unsigned long) comp_kernel_name);
|
ワークブロックのセットアップ
このセクションではどうやってワークブロックを作成するかを説明しています。プログラムはワークブロックを作成した後、入力と出力を各ワークブロックに対応付けます。各ワークブロックへは、入力行列のうちmatrix[row][0]から始まるサイズH * Vのブロックの内容を記述しています。HとVはブロックの水平方向および垂直方向の次元を表しています。
この例では、アクセラレータのメモリにH * V要素の入力行列を2つ、H * V要素の出力行列を1つ格納できることを想定しています。プログラムはalf_wb_enqueue()関数を呼び、ワークブロックをキューに追加して処理させます。ALFは即時ランタイムモードを採用しています。最初のワークブロックがキューに追加されたらすぐにタスクはワークブロックの処理を開始します。alf_task_finalize関数でワークブロックのキューイングを終了します。
alf_wb_handle_t wb_handle;
add_parms_t parm __attribute__((aligned(128)));
parm.h = H; /* horizontal size of the block */
parm.v = V; /* vertical size of the block */
/* creating work blocks and adding parameter & io buffer */
for (i = 0; i < NUM_ROW; i += H) {
alf_wb_create(task_handle, ALF_WB_SINGLE, 0,&wb_handle);
/* begins a new Data Transfer List for INPUT */
alf_wb_dtl_set_begin(wb_handle, ALF_BUF_IN, 0);
/* Add H*V element of mat_a as Input */
alf_wb_dtl_set_entry_add(wb_handle, &matrix_a[i][0], H * V, ALF_DATA_FLOAT);
/* Add H*V element of mat_b as Input */
alf_wb_dtl_set_entry_add(wb_handle, &matrix_b[i][0], H * V, ALF_DATA_FLOAT);
alf_wb_dtl_set_end(wb_handle);
/* begins a new Data Transfer List OUTPUT */
alf_wb_dtl_set_begin(wb_handle, ALF_BUF_OUT, 0);
/* Add H*V element of mat_c as Output */
alf_wb_dtl_set_entry_add(wb_handle, &matrix_c[i][0], H * V, ALF_DATA_FLOAT);
alf_wb_dtl_set_end(wb_handle);
/* pass parameters H and V to spu */
alf_wb_parm_add(wb_handle, (void *) (&parm), sizeof(parm), ALF_DATA_BYTE, 0);
/* enqueuing work block */
alf_wb_enqueue(wb_handle);
}
alf_task_finalize(task_handle);
|
タスクの待機と終了
全てのワークブロックが処理キューに追加されたら、プログラムはアクセラレータがワークブロックの処理を完了するのを待ちます。そしてalf_exit()を呼びだして、ALFランタイム環境の後片付けと終了を行います。
/* waiting for all work blocks to be done*/
alf_task_wait(task_handle, -1);
/* exit ALF runtime */
alf_exit(alf_handle, ALF_EXIT_WAIT, -1);
|
アクセラレータ側の準備
アクセラレータ側では、実際に2つの行列の和を計算する計算カーネルを用意する必要があります。アクセラレータ側のALFランタイムは、ユーザが準備したalf_accel_comp_kernel関数を呼び出す前に、入力バッファをアクセラレータのメモリに読み込みます。alf_accel_comp_kernelから戻った後は、ALFランタイムは出力データをホストのメモリ空間に戻します。
ダブルバッファリングまたはトリプルバッファリングを適切に適用することで、入力バッファがアクセラレータメモリに書き込まれるまでのレイテンシおよび出力バッファがホストメモリ空間に書き込まれるまでのレイテンシを計算によって良く覆い隠せるようになっています。
int alf_accel_comp_kernel(void *p_task_context,
void *p_parm_context,
void *p_input_buffer,
void *p_output_buffer,
void *p_inout_buffer,
unsigned int current_count,
unsigned int total_count)
{
unsigned int i, cnt;
vector float *sa, *sb, *sc;
add_parms_t *p_parm = (add_parms_t *)p_parm_context;
cnt = p_parm->h * p_parm->v / 4;
sa = (vector float *) p_input_buffer;
sb = sa + cnt;
sc = (vector float *) p_output_buffer;
for (i = 0; i < cnt; i += 4) {
sc[i] = spu_add(sa[i], sb[i]);
sc[i + 1] = spu_add(sa[i + 1], sb[i + 1]);
sc[i + 2] = spu_add(sa[i + 2], sb[i + 2]);
sc[i + 3] = spu_add(sa[i + 3], sb[i + 3]);
}
return 0;
}
|
アクセラレータデータ分割の例を見てみよう
トリックのようですね。本当に終わりなんです。ワークブロックの作成を除けば、ホストのコードは変更ありません。おっと、まだありました。アクセラレータデータ分割を使用することをタスクデスクリプタに設定する必要があります。alf_accel_input_dtl_prepare関数とalf_accel_output_dtl_prepare関数の実装が必要です。
このサンプルの完全なソースコードは、ALFサンプルディレクトリのmatrix_add/common/accel_partitioningを参照してください。
参考文献 学ぶために
製品や技術を入手するために
議論するために
著者について  | 
|  | Kane Scarlett は技術ジャーナリスト/アナリストとして20年の経験があり、National Geographic, Population Reference Bureau, Miller Freeman, IDGで記事を書いています。また、恐れ多くも各種のジャーナル誌、JavaWorld、LinuxWorld、そしてもちろんdeveloperWorksへの記事の管理、編集、執筆を行っています。 |
記事の評価
|  |