Cellで複数のSPEに同じ演算を独立にさせる

やることは、PPEで適当な数値を定義して、それを4つのSPEにバラまいて、それぞれのSPEでバラまかれた絶対値の計算をして、それをPPEに回収するということ。これってMPI使って流体計算するのの基本なので、とりあえずこれができれば先は可からなさげな気がする。あとはSPE同士の通信をするっていうのだけでいけるはず。
とりあえずチュートリアルからコード丸写しで、動かして理解してみた。
最初にPPE用プログラム

PPE用のプログラム(test_ppe.c)

#include <stdio.h>
#include <stdlib.h>
#include <libspe2.h>
#include <pthread.h>    //  沢山スレッドを作るための何かっぽい

#define NUM_SPE 4          //  利用するSPE数
#define SIZE    (64)       //  とりあえず使う配列要素数

float in[SIZE]  __attribute__((aligned(16))) = {  1,  -2,  3,  -4,  5,  -6,  7,  -8,
						  9, -10, 11, -12, 13, -14, 15, -16,
                                                  17, -18, 19, -20, 21, -22, 23, -24,
                                                  25, -26, 27, -28, 29, -30, 31, -32,
                                                  33, -34, 35, -36, 37, -38, 39, -40,
                                                  41, -42, 43, -44, 45, -46, 47, -48,
                                                  49, -50, 51, -52, 53, -54, 55, -56,
                                                  57, -58, 59, -60, 61, -62, 63, -64 };
    //  SPEにバラまく予定の、絶対値を計算させる値の初期化とか宣言とか

float out[SIZE] __attribute__((aligned(16)));
    //  SPEに計算させたのを格納する配列の宣言


typedef struct {
  unsigned long long  ea_in;
  unsigned long long  ea_out;
  unsigned int        size;
  int                 pad[3];
} abs_params_t;                //  SPEに計算させる配列の特性をまとめた構造体の定義

abs_params_t abs_params[NUM_SPE] __attribute__((aligned(16)));     //  SPEに計算させる構造体の宣言

typedef struct {
  spe_context_ptr_t   spe;
  abs_params_t        *abs_params;
} thread_arg_t;             //  for multiple SPE management
                     //   複数SPEを使うときのSPEを管理するパラメータをまとめた構造体の定義

//  複数のSPEに絶対値を計算させるのを関数にしたの
void *run_abs_spe (void *thread_arg) {
  int ret;
  thread_arg_t *arg = (thread_arg_t *) thread_arg;
     //  SPEの配列を管理するときのパラメータをmain文で受け取るときにスレッドの情報をメモリのアドレスでもらうとか、そんな感じ?よくわからん
  unsigned int entry;
  spe_stop_info_t stop_info;
  
  entry = SPE_DEFAULT_ENTRY;
  ret = spe_context_run(arg->spe, &entry, 0, arg->abs_params, NULL, &stop_info);
     //  spe_context_runでSPEを走らせる

  return NULL;
}

int main(int argc, char **argv) {
  int i;
  int ret;
  
  spe_program_handle_t *prog;      //  SPEプログラムのイメージの宣言
  spe_context_ptr_t spe[NUM_SPE];  //  SPEプログラムのコンテキストの宣言
  pthread_t thread[NUM_SPE];       //  SPE計算させるときのスレッドの宣言
  thread_arg_t arg[NUM_SPE];       //  SPEの配列を管理する構造体の宣言(グローバルに定義したのを使うっぽい)
  
  prog = spe_image_open("test_spe.elf");   //  SPEプログラムを開く

  for (i = 0; i < NUM_SPE; i++) {          //  0番目からNUM_SPE番めのSPEにSPEプログラムをバラまく
    spe[i] = spe_context_create(0, NULL);
    ret = spe_program_load(spe[i], prog);
  }

  int size = SIZE/NUM_SPE;                 
       //  それぞれのSPEの使う配列をsizeとして定義する
       //  PPEではSIZEとしていたのを、SPEをNUM_SPE個のSPEを使うので、SPEでの配列のサイズは
       //  SIZE / NUM_SPEだけで済むよというお話

  //  0番目からNUM_SPE-1番目のSPEへ絶対値を計算させる数値をバラまくための準備をする
  //  全てのSPEへこれからバラまく値を格納する配列のサイズを伝えるような作業
  for (i = 0; i < NUM_SPE; i++) {   
    abs_params[i].ea_in  = (unsigned long) &in[i*size];    //  PPEのメインメモリ上の配列のどこから
    abs_params[i].ea_out = (unsigned long) &out[i*size];   //  どこまでの値が
    abs_params[i].size   = size;                           //  どれだけの大きさになるか
       
    arg[i].spe = spe[i];                  //  それぞれのSPEへ渡すパラメータをSPEごとにコピー
    arg[i].abs_params = &abs_params[i];   //  うまくは説明できないけど、ないと困りそう

     ret = pthread_create(&thread[i], NULL, run_abs_spe, &arg[i]);
     //  SPEに計算させるスレッドを作って、ついでに実行
     //  絶対値の計算をSPEにさせるのはrun_abs_spe関数で行う
     //  関数の呼出は、関数ポインタをスレッドを生成する関数へ渡すことで行う(賢い)
  }

  for (i = 0; i < NUM_SPE; i++) {  //  計算が終わったらスレッドを閉じます
      pthread_join(thread[i], NULL);
      ret = spe_context_destroy(spe[i]);
  }

  ret = spe_image_close(prog);

  for (i = 0; i < SIZE; i++) {    //  結果の表示
      printf("out[%02d]=%0.0f\n", i, out[i]);
  }

  return 0;
http://cell.fixstars.com/ps3linux/index.php/3.5%E3%80%80%E8%A4%87%E6%95%B0SPE%E3%82%92%E5%88%A9%E7%94%A8%E3%81%97%E3%81%9F%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E5%AE%9F%E8%A1%8C
}

どうやら流れとしては、

  1. 複数のSPEを使うためのパラメータを設定
  2. SPEに計算させる値をそれぞれのSPEごとに作ってやる
  3. SPEのスレッドを作る(ついでに実行)

とかそんな感じっぽい。この辺はMPIに似てる気がする。


次にSPEの方は、DMA転送という方法を使ってPPEから値を引っ張ってくるらしい。
どうやらDMA転送というのは計算機の中のパーツ同士の通信に使う形式でどうのこうのというものらしい。速いらしい。そして、PPEからSPEの使っているメモリ領域へは直接アクセスできないっぽいので、SPEの方がPPEの管理しているメインメモリの方へ値をもらいにいくらしい。
その時にDMA転送というのを使うっぽい。
そして、SPEはLSという固有のメモリ領域を持っていて、これとSPEの間の通信が馬鹿みたいに速いらしい、ので、Cellは浮動小数点計算が速いですよということみたい。わかるようなわからないような。

SPEのコード(test_spe.c)

#include <stdio.h>
#include <stdlib.h>
#include <spu_intrinsics.h>
#include <spu_mfcio.h>       //  SPEのメモリ管理に必要なライブラリのインクルード

#define MAX_BUFSIZE (128)    //  とりあえず配列の最大要素数を決めておく


float in_spe[MAX_BUFSIZE]  __attribute__((aligned(16)));  //  PPEから引っ張ってくる配列の宣言
float out_spe[MAX_BUFSIZE] __attribute__((aligned(16)));  //  PPEへ送り返す配列の宣言

typedef struct {             //  PPEから流れてくる配列の数のパラメータをまとめた構造体の定義
  unsigned long long  ea_in;
  unsigned long long  ea_out;
  unsigned int        size;
  int                 pad[3];
} abs_params_t;

abs_params_t abs_params __attribute__((aligned(16)));
  //  PPEから流れてくる配列の数のパラメータをまとめた構造体の宣言

int main(unsigned long long spe, unsigned long long argp, unsigned long long envp)
{
  int i;
  int tag = 1;

  /* DMA Transfer 1 : GET input/output parameters */
  //  最初にもらってくる配列のサイズなりのパラメータを受け取る
  spu_mfcdma64(&abs_params, mfc_ea2h(argp), mfc_ea2l(argp),
	       sizeof(abs_params_t), tag, MFC_GET_CMD);
     //  abs_paramsから後ろに、mfc_ea2h(argp)で求めたargpの頭から、
     //  mfc_ea2l(argp)で求めたargpのまでの、
     //  サイズがabs_params_tの値が入るよということらしい
     //  MFC_GET_CMDを指定すると、PPEから値を取ってくる

  spu_writech(MFC_WrTagMask, 1 << tag);
  spu_mfcstat(MFC_TAG_UPDATE_ALL);
  
  /* DMA Transfer 2 : GET input data */
  spu_mfcdma64(in_spe, mfc_ea2h(abs_params.ea_in), mfc_ea2l(abs_params.ea_in),
	       abs_params.size * sizeof(float), tag, MFC_GET_CMD);
     //  in_speから後ろに、abs_params.ea_inの頭から尻尾までの、abs_params.size分だけの数値を、
     //  MFC_GET_CMDなので、引っ張ってくるよ

  spu_writech(MFC_WrTagMask, 1 << tag);
  spu_mfcstat(MFC_TAG_UPDATE_ALL);
  
  /* Calculate absolute values */
  //  絶対値を計算する
  for (i = 0; i < abs_params.size; i++) {
    if (in_spe[i] > 0) {
      out_spe[i] = in_spe[i];
    } else {
      out_spe[i] = in_spe[i] * -1;
    }
  }
  
  /* DMA Transfer 3 : PUT output data */
  //  これまでの容量と同じようにして、送り返すよということ
  //  今度はMFC_PUT_CMDで送り返すよ
  spu_mfcdma64(out_spe, mfc_ea2h(abs_params.ea_out), mfc_ea2l(abs_params.ea_out),
	       abs_params.size * sizeof(float), tag, MFC_PUT_CMD);
  spu_writech(MFC_WrTagMask, 1 << tag);
  spu_mfcstat(MFC_TAG_UPDATE_ALL);
  
  return 0;
}
http://cell.fixstars.com/ps3linux/index.php/3.5%E3%80%80%E8%A4%87%E6%95%B0SPE%E3%82%92%E5%88%A9%E7%94%A8%E3%81%97%E3%81%9F%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E5%AE%9F%E8%A1%8C

ということで、PPEからSPEへ値をバラまくというのができてしまったので、今度はSPE同士の通信をマスターすればあとはCellのSPEを活用した数値計算ができそう。とりあえず拡散方程式くらいはできるだろうなあと。
んと、どうやらそれをやるにはSNRというのを使うらしい。
これは来週以降するかなあと思います。とりあえず今週は風邪っぽかったので引きこもってみた。


しかしCellはヘテロジニアスな構成なので、これまでMPIを使って、どのコアも同じようにというような哲学で作られたものでやってきた身としては少し慣れない感じである。
どっちがいいかって、MPIは仕様が簡単で、とっつきやすいっていうのはあるけど、結局はCellでやるようにどれかのコアが親玉にならなければならず、そこで多少オーバヘッドが生じるかもしれないというのはある。
それでもそれぞれのコアに十分に沢山メモリを割り当てられたので、大規模な計算ができるわけだけれどね。Cellの場合はそうはいかないので、大規模な計算には向かなさげ。メインメモリをでかくしたところで、それをSPEが直接とりにいけないとなると困るよなあ。どうにかしてほしいもんです。しかしそうすると今度はCPUだけで計算機が完結してしまうよというような話になるのだよなあ。
駄菓子菓子、大規模な計算をするなら石1つにつきメモリは1GBバイトは欲しいのが最近の傾向なので、なんともかんとも。とりあえず1MBでもいいからLSを増やしてほしいと思う感じです。