3.5 複数SPEを利用したアプリケーション実行
出典: PS3 Linux Information Site / Cell/B.E.のパワーを体験しよう
これまで紹介してきた例題プログラムはすべてSPEを1基だけ利用するものでした。ここでは、複数のSPEを利用して、アプリケーションを実行する方法について解説します。
3.5.1 複数SPEへの処理の分割
Cellプログラミングでは、複数存在するSPEへアプリケーションの処理を分割して割り振ることが非常に重要になります。アプリケーションの処理をSPEへ割り振る方法はいくつも提案されていますが、本チュートリアルでは、アプリケーションが処理するデータを分割して、複数のSPEで並列に同じ処理を実行するモデルについて解説します。
データ分割型のアプリケーション・モデルでは、SPEで実行されるプログラムは同じものを利用し、処理するデータだけをPPEプログラム側で分割して、SPEプログラム側に分割されたデータを割り振ります。図 3.6に複数SPEへのデータ分割のイメージを示します。
以降では、具体的なプログラミング方法について説明します。
3.5.2 複数SPEを利用した絶対値計算プログラム
ここでは、第3.3節、第3.4節で取り上げてきた絶対値計算プログラムを例に、複数SPEを利用したプログラミング方法について見ていきます。これらの絶対値計算プログラムは、64個の入力データの中から、負の整数値を正の整数値に変換するプログラムでした。さらに、ここでは図 3.7に示すように64個の入力データを4基のSPEに均等に割り振って並列に絶対値を計算するように書き換えていきます。
PPEプログラム側でのSPEプログラムの実行方法については、単一SPEプログラムの実行方法と基本的には同じです。ただし、利用するSPEの個数だけ、SPEコンテキストを生成し、SPEプログラムを実行してあげる必要があります。また、SPEプログラムの実行には、spe_context_run()関数を利用しますが、この関数はSPEプログラムの実行が終了するまで戻らない関数なので、スレッドライブラリを利用して並列化しなければなりません。
N個のSPEを利用したプログラムの基本的な実行手順は、以下のようになります。
(1) SPEプログラム・イメージをオープンする
(2) (複数SPE対応) N個のSPEコンテキストを生成する
(3) (複数SPE対応) SPEプログラムをN個のLSへロードする
(4) (複数SPE対応) N個のSPEプログラム実行用スレッドを生成する
(5) (複数SPE対応) 各スレッドでSPEプログラムを実行する
(6) (複数SPE対応) N個のスレッドすべての終了を待つ
(7) (複数SPE対応) N個のSPEコンテキストを破棄する
(8) SPEプログラム・イメージをクローズする
なお、データ分割型のアプリケーション・モデルでは、すべて同じSPEプログラム・イメージを利用するため、(1) と (8) については1度だけの呼び出しで問題ありません。
(1) SPEコンテキストの生成とSPEプログラムのロード
まず、複数SPEを利用するためには、アプリケーションで利用するSPEの個数分だけSPEコンテキストを生成します。続いて、各SPEのLSへSPEプログラムをロードします。本チュートリアルでは、各SPEのLSへロードするプログラムは、すべて同じプログラム・イメージを利用します。リスト (3-7) に、具体的なプログラミング例を示します。
リスト (3-7) SPEコンテキストの生成とSPEプログラムのロード
1 for (i = 0; i < NUM_SPE; i++) {
2 spe[i] = spe_context_create(0, NULL);
3 if (!spe[i]) {
4 perror("spe_context_create");
5 exit(1);
6 }
7
8 ret = spe_program_load(spe[i], prog);
9 if (ret) {
10 perror("spe_program_load");
11 exit(1);
12 }
13 }
(2) SPEプログラムへのデータ分割
SPEプログラムを実行する前に、各SPEプログラムへ渡すデータを分割しておきます。各SPEプログラムにデータを分割して割り振る手法ついては、アプリケーションによって異なります。リスト (3-9) は、絶対値計算プログラムで利用するデータを分割している例です。
リスト (3-9) SPEプログラムへのデータ分割
1 int size = SIZE/NUM_SPE;
2 for (i = 0; i < NUM_SPE; i++) {
3 abs_params[i].ea_in = (unsigned long) &in[i*size];
4 abs_params[i].ea_out = (unsigned long) &out[i*size];
5 abs_params[i].size = size;
6 }
ここでは、絶対値計算で利用する64個のデータを分割し、入力データinと出力先バッファoutの実効アドレスを適切に調整し、SPEプログラムへ渡すパラメータ・セットabs_paramsとして格納しています。
(3) SPEプログラム実行用スレッドの生成
続いて、SPEプログラムを実行するためのスレッドを生成します。本チュートリアルでは、スレッドライブラリとしてPOSIXスレッド (pthread) を利用し、pthread_create()関数によりスレッドを生成します。
リスト (3-8) に、具体的なプログラミング例を示します。
リスト (3-8) SPEプログラム実行用スレッドの生成
1 for (i = 0; i < NUM_SPE; i++) {
2 arg[i].spe = spe[i];
3 arg[i].abs_params = &abs_params[i];
4
5 ret = pthread_create(&thread[i], NULL, run_abs_spe, &arg[i]);
6 if (ret) {
7 perror("pthread_create");
8 exit(1);
9 }
10 }
pthread_create()関数の第3引数には、スレッド開始関数への関数ポインタを指定し、第4引数には、スレッド開始関数へ渡される引数を指定します。ここでは、スレッド生成時にrun_abs_spe()関数を実行し、その関数の引数に変数arg[i]へのポインタを渡します。変数arg[i]には、SPEプログラムの実行に必要となるパラメータ (SPEコンテキストと絶対値計算の分割データ) を設定します。
(4) SPEプログラムの並列実行
生成された各スレッドでは、スレッド開始関数の中でspe_context_run()関数を呼び、SPEプログラムを実行します。SPEプログラムの実行方法は、単一SPEのプログラム実行方法と同じです。
リスト (3-10) に、具体的なプログラミング例を示します。
リスト (3-10) SPEプログラムの並列実行
1 void *run_abs_spe(void *thread_arg)
2 {
3 int ret;
4 thread_arg_t *arg = (thread_arg_t *) thread_arg;
5 unsigned int entry;
6 spe_stop_info_t stop_info;
7
8 entry = SPE_DEFAULT_ENTRY;
9 ret = spe_context_run(arg->spe, &entry, 0, arg->abs_params, NULL, &stop_info);
10 if (ret < 0) {
11 perror("spe_context_run");
12 return NULL;
13 }
14
15 return NULL;
16 }
SPEプログラムが終了すると、spe_context_run()関数から各スレッドに実行が戻ってきて、スレッドが終了します。
(5) SPEプログラム実行用スレッドの終了とSPEコンテキストの破棄
すべてスレッドの終了を待つために、pthread_join()関数を利用します。スレッドが終了したら、不要になったSPEコンテキストを破棄します。
リスト (3-10)
1 for (i = 0; i < NUM_SPE; i++) {
2 pthread_join(thread[i], NULL);
3 ret = spe_context_destroy(spe[i]);
4 if (ret) {
5 perror("spe_context_destroy");
6 exit(1);
7 }
8 }
3.5.3 例題プログラムの解説
それでは、例題プログラムのソースコード全体について解説します。SPEプログラムは、例題プログラム (3-5) とまったく同じプログラムであるため解説は省略します。
例題プログラム (3-6) 複数SPEを利用した絶対値計算プログラム (PPE用プログラム)
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <libspe2.h>
4 #include <pthread.h>
5
6 #define NUM_SPE 4
7 #define SIZE (64)
8
9 float in[SIZE] __attribute__((aligned(16))) = { 1, -2, 3, -4, 5, -6, 7, -8,
10 9, -10, 11, -12, 13, -14, 15, -16,
11 17, -18, 19, -20, 21, -22, 23, -24,
12 25, -26, 27, -28, 29, -30, 31, -32,
13 33, -34, 35, -36, 37, -38, 39, -40,
14 41, -42, 43, -44, 45, -46, 47, -48,
15 49, -50, 51, -52, 53, -54, 55, -56,
16 57, -58, 59, -60, 61, -62, 63, -64 };
17 float out[SIZE] __attribute__((aligned(16)));
18
19 typedef struct {
20 unsigned long long ea_in;
21 unsigned long long ea_out;
22 unsigned int size;
23 int pad[3];
24 } abs_params_t;
25
26 abs_params_t abs_params[NUM_SPE] __attribute__((aligned(16)));
27
28 typedef struct {
29 spe_context_ptr_t spe;
30 abs_params_t *abs_params;
31 } thread_arg_t;
32
33 void *run_abs_spe(void *thread_arg)
34 {
35 int ret;
36 thread_arg_t *arg = (thread_arg_t *) thread_arg;
37 unsigned int entry;
38 spe_stop_info_t stop_info;
39
40 entry = SPE_DEFAULT_ENTRY;
41 ret = spe_context_run(arg->spe, &entry, 0, arg->abs_params, NULL, &stop_info);
42 if (ret < 0) {
43 perror("spe_context_run");
44 return NULL;
45 }
46
47 return NULL;
48 }
49
50 int main(int argc, char **argv)
51 {
52 int i;
53 int ret;
54
55 spe_program_handle_t *prog;
56 spe_context_ptr_t spe[NUM_SPE];
57 pthread_t thread[NUM_SPE];
58 thread_arg_t arg[NUM_SPE];
59
60 prog = spe_image_open("vec_abs_spe.elf");
61 if (!prog) {
62 perror("spe_image_open");
63 exit(1);
64 }
65
66 for (i = 0; i < NUM_SPE; i++) {
67 spe[i] = spe_context_create(0, NULL);
68 if (!spe[i]) {
69 perror("spe_context_create");
70 exit(1);
71 }
72
73 ret = spe_program_load(spe[i], prog);
74 if (ret) {
75 perror("spe_program_load");
76 exit(1);
77 }
78 }
79
80 int size = SIZE/NUM_SPE;
81 for (i = 0; i < NUM_SPE; i++) {
82 abs_params[i].ea_in = (unsigned long) &in[i*size];
83 abs_params[i].ea_out = (unsigned long) &out[i*size];
84 abs_params[i].size = size;
85
86 arg[i].spe = spe[i];
87 arg[i].abs_params = &abs_params[i];
88
89 ret = pthread_create(&thread[i], NULL, run_abs_spe, &arg[i]);
90 if (ret) {
91 perror("pthread_create");
92 exit(1);
93 }
94 }
95
96 for (i = 0; i < NUM_SPE; i++) {
97 pthread_join(thread[i], NULL);
98 ret = spe_context_destroy(spe[i]);
99 if (ret) {
100 perror("spe_context_destroy");
101 exit(1);
102 }
103 }
104
105 ret = spe_image_close(prog);
106 if (ret) {
107 perror("spe_image_close");
108 exit(1);
109 }
110
111 for (i = 0; i < SIZE; i++) {
112 printf("out[%02d]=%0.0f\n", i, out[i]);
113 }
114
115 return 0;
116 }
| 28行目~31行目 | SPEプログラム実行用スレッドにデータを渡すためのデータ型thread_arg_tを定義します。構造体メンバspeはSPEコンテキストを保持し、構造体メンバabs_paramsはSPEプログラムへ渡すパラメータ・セットのポインタを保持します。 |
| 32行目~44行目 | SPEプログラム実行用スレッドから、絶対値を計算するSPEプログラムを実行します。 |
| 66行目~78行目 | 4基のSPEでSPEプログラムを実行するために、SPEコンテキストを4個作成し、SPEプログラムを各SPEのLSへロードします。 |
| 80行目~94行目 | 各SPEに振り分けるために、絶対値計算プログラムへ渡すデータを分割します。分割したデータと、SPEコンテキストを引数として、pthread_create()関数を利用してSPEプログラム実行用スレッドを生成します。 |
| 96行目~103行目 | pthread_join()関数を利用して、すべてのSPEプログラム実行用スレッドの終了を待ちます。スレッドが終了したら、不要になったSPEコンテキストを破棄します。 |
複数SPEを利用した絶対値計算プログラムのソースコードは以下のリンクからダウンロードできます。
ファイルダウンロード:multi_vec_abs.tar.gz
| 「第3.4節」へ戻る | 「第3章目次」 | 「演習問題 (3-1)」へ進む |
| 「チュートリアル目次」 |


