IBM®
本文へジャンプ
    Japan [変更]    ご利用条件
 
 
検索範囲検索:    
    ホーム    製品    サービス & ソリューション    サポート & ダウンロード    マイアカウント    
skip to main content

developerWorks Japan  >  Multicore acceleration  >

Cell Superscalar はどうだい?: 第 1 回 まずは使ってみよう

developerWorks
ページオプション

JavaScript を要するドキュメントオプションは表示されません


レベル: 中級

浅原 明広 (asahara@fixstars.com), 株式会社FIXSTARS 技術開発本部長 / AKD48

2009年 01月 16日

演算コアを複数持つ、いわゆるマルチコアプロセッサはごく一般的なものとなりましたが、それら複数の演算コアを効率よく利用するためには、従来のソフトウエアをなんらかの方法で並列化してやる必要があります。 近年、そのような並列化プログラミング開発を効率よく利用するためのツールやフレームワークが多くの企業・研究機関から発表されています。本連載では、それら並列化フレームワークの一つである、Cell Broadband Engine 用フレームワーク "Cell Superscalar" を取り上げます。

はじめに

Sony, Toshiba, IBM によるCell Broadband Engine(以下Cell/B.E.) をはじめ、NVIDIAのTesla、AMDのFire Stream、など、「アクセラレータ」と呼ばれるボードが存在感を増してきました。汎用プロセッサの分野でも、いわゆるNative Quad Core と呼ばれる、ダイ上でのQuad Core が実現されるようになりました。これは、スーパーコンピューターや業務用サーバーに限った話ではなく、例えば今皆さんがお使いのPCも、おそらく2Core以上のCPUを持っているものがほとんどだと思います。

このように、ハードウエアとしてのマルチコアプロセッサは大変身近なものとなりましたが、ソフトウエアについてはどうでしょうか?プロセッサの周波数が上がれば勝手にソフトウエアの処理時間が改善された時代とは異なり、現代は周波数は据え置きでコア数が増えていく時代です。並列化されていないソフトウエアはコア数増加の恩恵に与ることができないため、新しいCPUの上で高速な動作を実現するためには、既存のソフトウエアになんらかの手を加えて並列化する必要があります。

当然、並列化されたコードを自分で書けば、最も高いパフォーマンスが得られるでしょう。しかしながら、既存のソフトウエアすべてに対し、ソースコードレベルで並列化を行うには、それなりの時間がかかります。また、開発にしても、デバッグにしても、通常の逐次処理のコードを書くのとは異なる感覚が必要で、そのスキル習得は容易ではありません。 既存のソースコードを自動で並列化してくれて、そこそこのパフォーマンス向上が得られるコンパイラがあれば大変助かるのですが、残念ながら、現状の主要なコンパイラの自動並列化オプションでは、まだまだ満足できる性能を得ることはできないでしょう。

そこで、並列化支援のためのフレームワークや半自動並列化ツールです。これらのツール群は、コンパイル一発で並列化を実現してくれるものではありませんし、ツール固有の文法や御作法を習得する必要があるものがほとんどですが、並列化の面倒な部分を隠蔽してプログラマーの負担を半減してくれます。また、ある程度人の手を介することで、コンパイラ任せの自動並列化に比べて、より大きなパフォーマンスの向上が期待できます。

前置きが長くなりましたが、本記事では、そのような並列化フレームワークのひとつである、Barcelona Supercomputing Center が開発した、"Cell Superscalar" (以下CellSs) を取り上げ、インストールから簡単なプログラム作成と、その性能評価までを行います。

CellSs 概要

それでは本題に入りましょう。今回取り上げるCellSsは、既存のソースコードに独自のpragma 文を付加し、SPEで実行させる計算を"task" として明示することで、複数のSPEで並列処理される効率のよいコードを自動で生成させるためのフレームワークです。taskのスケジューリングや、taskが読み書きするデータの受け渡しはCellSs フレームワークの方で面倒をみてくれるので、ユーザーは意識する必要はありません。なお、CellSsはその名の通り、当初Cell/B.E.のために開発されていましたが、現在ではホモジニアスなマルチコアプロセッサへ対応した"SMP Superscalar"も発表されていて、CellSs と同じ記述で、汎用SMPシステム向けの並列化開発を行うことができます。

図 1 にCellSsにおける、アプリケーションのビルドプロセスを示します。 CellSsフレームワークは大きく分けて2つのコンポーネントから成ります。まずひとつ目は、Source-to-source コンパイラです。CellSs特有のpragma文が付加されたソースコード(図中app.c) は、まずSource-to-sourceコンパイラで分析・処理されて、PPE用とSPE用の2つのコードに変換されます。これら2つのコードのコンパイルには、GNU系のppu-gcc, spu-gcc、あるいは IBM XLC系の ppuxlc, spuxlc などの外部ツールが利用されます。生成されたオブジェクトファイルはCellSsの2つ目のコンポーネントであるランタイムライブラリと結合されます。SPE側の実行形式ファイルはライブラリ化されて、PPE側のオブジェクト群と結合され、最終的にCell/B.E.での実行形式ファイルが生成されます。

以上のように、ビルドプロセスは少々複雑ですが、ユーザーはプロセスを意識する必要はありません。基本的には "cell-ss" というコマンドが中間ファイルの生成から最終的な実行形式ファイルのリンクまでの一連のプロセスを自動で行ってくれます。


図 1.CellSsビルドプロセス
CellSsビルドプロセス

さて、特有のpragma文…と聞くと、以前developer worksで紹介した IBM XLC Single Source Compiler(以下XLC SSC) によるOpenMPを思い出します。確かに書式は似ていますし、XLC SSCでも、PPE用、SPE用の2つのソースコードを書くことなく、一つのソースファイルから実行形式ファイルを生成することができました。しかし、CellSsとOpenMP のコンセプトはかなり異なるものです。

OpenMPでは、どの部分を並列化して、どの部分を逐次処理するのか、プログラマーが明示的に指定してやる必要がありました。しかし、CellSsでプログラマーが指定するのは、独立な機能ブロックであるtaskです。CellSsのSource-to-sourceコンパイラがtask間のデータ依存性を分析し、並列に処理できるtaskを自動で見つけてくれますし、多数のtaskのスケジューリングもCellSsにお任せにできます。かいつまんで言えば、OpenMPはデータ並列処理に向いていて、CellSsはタスク並列処理に向いている、ということです。もちろん、OpenMPでも#pragma omp section ディレクティブを利用して、タスク並列で処理させることは可能ですが、その場合、タスクスケジューリングはプログラマーが実装する必要があります。CellSs開発者によれば、将来的にはOpenMPとCellSsの統合もプランにあるようです。

その他、現状でXLC SSCによるOpenMPよりもCellSsが優れていると思われる点をあげてみます。

  • SPU 組み込み関数が利用できる

    XLC SSCでは、並列化ブロック内部でspu_addなどのSPU組み込み関数を使用することができないため、Single Instraction Multiple Data (SIMD)処理については、コンパイラの自動ベクトル化オプションを頼るしかありません。CellSsでは、Taksブロック内部でSPU組み込み関数を自由に使うことができます。

  • 中間生成物を見ることができる

    XLC SSCでも、ビルドプロセス中に部分的なSPEのオブジェクトや実行形式が生成されているはずですが、ユーザーが得られるのは最終的なCell/B.E.実行形式のみです。CellSsの場合、コンパイルオプション"-k"を付けてやることで、Source-to-sourceコンパイラの出力のCソースコード群やSPEの実行形式ファイルなど、中間生成物を全て保存する(keepするので"-k")ことができます。もちろん、中間生成物のCソースコードをいじることもできますので、より細かなチューンナップが可能です。

  • Fortran のサポート

    現在のバージョンのIBM XL Fortran には、Single Source Compiler の機能はありません。CellSsでは"IBM XL Fortran multicore acceleration for Linux on System p. V11.1"がインストールされていることを前提として、Fortranコードもサポートしています。ただし、Fortranを使う場合は、C言語とは異なりいくつか制限事項がありますので、User's manual 等で確認が必要です。

  • フリーソフトウエアである

    IBM XLC SSCは2008年11月にα版を卒業し、有償の製品となりました。60日間のトライアル版を無償で利用することはできますが、継続的な利用にはライセンス料を支払う必要があります。もちろん、ライセンス料を支払う製品であることが、逆にメリットとなる場合もあるとは思います。なお、CellSs には、GPL/LGPLライセンスが適用されています。

インストール

CellSsの最新バージョンは2.1 であり、本家Barcelona Supercomputing CenterのCellSsサイトでは、CellSsのtarballをダウンロードできますが、tarボールに含まれるのはあくまでもCellSsソースコードなので、まずconfigure を使ってMakefile を作りmake;make installしてやる必要があり、インストールにはそれなりの時間がかかります。

PLAYSTATION 3(以下PS3),IBM BladeCenter QS21/22 をはじめ、各種Cell/B.E.ハードウエアに対応したLinux OSであるYellow Dog Linux 6.1(以下、YDL) には、インストールDVDにコンパイル済みのCellSs が収録されていますので、こちらを利用すると導入が楽です。YDLはこちらのURLから フリーでダウンロードできます。

ただし、PS3にYDLを導入した初期状態ではCellSsはインストールされておりませんので、YDLインストール後、別途手動でインストールしてやる必要があります。YDLのインストールDVDに、"cellss-2.1-1.ydl6.2.ppc64.rpm"という名前のRPMパッケージとして収録されておりますので、そちらを使って、

#rpm -ivh cellss-2.1-1.ydl6.2.ppc64.rpm

をroot権限で実行します。もし、インストールの途中で

エラー: 依存性の欠如:
        libspe-devel は cellss-2.1-1.ydl6.2.ppc64 に必要とされています

というエラーが出てしまう場合は、同じくYDLのインストールDVDに収録されている"libspe-devel-2.2.80-132.ydl6.1.ppc.rpm" を先にインストールしましょう。

#rpm -ivh libspe-devel-2.2.80-132.ydl6.1.ppc.rpm

無事インストールが完了すると、/usr/local/bin 以下に、CellSs の本体であるcellss-cc が置かれます。 その他にも

  • バイナリファイルは /usr/bin/
  • インクルードファイルは /usr/include
  • ライブラリは/usr/lib
  • サンプルやドキュメント類は /usr/share/cellss, /usr/share/doc/cellss

に多数のファイルが追加されます。




上に戻る


再びのEuler 粒子シミュレーション

では、さっそく簡単なプログラムを書いてCellSsの効果を検証してみましょう。XLC SSCの紹介記事でもベンチマークとして使用した、Euler 粒子シミュレーションのプログラムを再び題材にします。


リスト 1. Euler法のサンプルプログラム (CellSsによる並列化)

001  #include <stdio.h> 
002  #include <stdlib.h> 
003  #include <sys/time.h> 
004  
005  struct  timeval tv1, tv2;
006  int elapsed_microsecs;
007  float elapsed_time;
008  
009  #define END_OF_TIME     300
010  #define PARTICLES       8192
011  #define BLOCK_SIZE      512
012  
013  /* 4-D vector definition. */
014  typedef struct {
015    float x, y, z, w;  
016  } vec4D;
017  
018  vec4D pos[PARTICLES];      // particle positions
019  vec4D vel[PARTICLES];      // particle velocities
020  vec4D force;               // current force being applied to the particles
021  float inv_mass[PARTICLES]; // inverse mass of the particles
022  float dt = 1.0f;           // step in time
023  
024  #pragma css task inout(pos, vel) input(inv_mass, force, dt)
025  void task_euler(vec4D pos[BLOCK_SIZE], vec4D vel[BLOCK_SIZE], 
026                  float inv_mass[BLOCK_SIZE], vec4D force, float dt)
027  {
028    // For each particle
029    for (int i=0; i < BLOCK_SIZE; i++) {
030      float dt_inv_mass = dt * inv_mass[i];
031      // For each step in time
032      for(float time=0.0;time< END_OF_TIME;time += dt){
033        pos[i].x = vel[i].x * dt + pos[i].x;
034        pos[i].y = vel[i].y * dt + pos[i].y;
035        pos[i].z = vel[i].z * dt + pos[i].z;
036  
037        vel[i].x = dt_inv_mass * force.x + vel[i].x;
038        vel[i].y = dt_inv_mass * force.y + vel[i].y;
039        vel[i].z = dt_inv_mass * force.z + vel[i].z;
040      }
041    }
042  }
043  
044  int main(int argc, char *argv[])
045  {
046    int i;
047    int m;
048    float xpos;
049    float ypos;
050  
051    //initialize
052    force.x = 0.0;
053    force.y = 0.0;
054    force.z = -9.80;  // [m/s]:gravity constant
055  
056    srand(111);
057  
058    for (i=0; i < PARTICLES; i++) {
059      pos[i].x = 0.0;
060      pos[i].y = 0.0;
061      pos[i].z = 0.0;
062  
063      vel[i].x = ((float)rand()/(RAND_MAX + 1.0)*2.0 - 1.0);
064      vel[i].y = ((float)rand()/(RAND_MAX + 1.0)*2.0 - 1.0);
065      vel[i].z = ((float)rand()/(RAND_MAX + 1.0)*2.0 - 1.0);// [m/s]
066  
067      inv_mass[i] = 0.001; // [kg]
068    }
069  
070    gettimeofday(&tv1,NULL);
071  #pragma css start
072    for (i=0; i < PARTICLES; i+= BLOCK_SIZE) {
073      task_euler(pos+i, vel+i, inv_mass+i, force, dt);
074    }
075  #pragma css finish
076    gettimeofday(&tv2,NULL);
077  
078    elapsed_microsecs =
079      (int)(tv2.tv_sec  - tv1.tv_sec) * 1000000 +
080      (int)(tv2.tv_usec - tv1.tv_usec);
081    elapsed_time = (float) elapsed_microsecs * 0.000001;
082  
083    printf("Elapsed Time = %f \n",elapsed_time);
084  
085    return (0);
086  }

どうでしょうか?このサンプルでは分割されたデータを持つtaskが並列で処理されてはいるものの、実はやっていることはデータ並列化とあまり変わりませんので、比較的理解しやすいと思います。 では、ソースコードを上から順に見ていきましょう。

  • 11行目: #define BLCK_SIZE 512

    OpenMPであれば分割するデータのサイズをコンパイラが決めてくれるので、プログラマーは意識する必要はありませんが、CellSsではtaskに割り当てるデータサイズを事前にプログラマーが決める必要があります。このサンプルプログラムでは、512個の粒子を1つのtaskに割り当てています。ちなみに、この粒子シミュレーションでは、粒子間の相互作用はないので、粒子毎に完全に並列化可能です。

  • 24行目: #pragma css task inout(pos, vel) input(inv_mass, force, dt)

    CellSsにおけるtask指示文であり、taskとしてSPE上で実行したい関数を指定します。PPE-SPE間のデータのやりとりはtask関数の引数で行いますが、CellSsでは、task関数の引数の種類を#pragma css taskに続く節で指定する必要があります。データの種類には3つあって、

    • input節は、task関数によって読み出されるデータを指定します。
    • output節は、task関数によって書き出されるデータを指定します。
    • inout節は、task関数によって読み書き両方が行われるデータを指定します。

    サンプルの例では、pos, vel 配列が読み書きされるデータ、inv_mass配列、force変数、dt変数が読み出しのみ行われるデータになります。

  • 25行目、26行目: void task_euler(vec4D pos[BLOCK_SIZE], vec4D vel[BLOCK_SIZE],float inv_mass[BLOCK_SIZE], vec4D force, float dt)

    task関数の返り値は、設定してもメインスレッドで受け取ることはできませんので、voidにしておきます。

  • 71行目: #pragma css start

    taskスケジューリングの開始を指定します。この指示文はOptional であり、もし#pragma css startが存在しない場合には、コンパイラが自動的にプログラムの最初に#pragma css startを追加します。

  • 72行目: for (i=0; i < PARTICLES; i+= BLOCK_SIZE)

    処理のメインループになります。BLOCK_SIZE毎にループを回しています。

  • 75行目: #pragma css finish

    taskスケジューリングの終了を指定します。この指示文はOptional であり、もし#pragma css finishが存在しない場合には、コンパイラが自動的にプログラムの最後に#pragma css finishを追加します。

なお、リスト1のコードをビルドするためのMakefileは以下のとおりです。


リスト 2. Makefile 例

CC = cellss-cc
LD = cellss-cc
CFLAGS = -O3

SOURCES = euler_cellss.c
BINARY = euler_cellss

$(BINARY) : $(SOURCES)

clean:
	rm *~ $(BINARY)

性能評価

では、リスト1のコードを使って性能評価を行ってみましょう。図 2に処理時間の測定結果を示します。 なお、どの測定点も、条件は以下の通りです。

  • パーティクル数: 8192個
  • シミュレーション時間: 300秒 (1step 1秒)
  • 使用ハードウエア: PLAYSTATION 3
  • 使用OS: Yellow Dog Linux 6.1
  • 使用SDK: Barcelona 版 Cell SDK 3.1
  • 使用SPE数: 4

図 2(左) では、比較対象として、XLC SSCでコンパイルした実行形式ファイルと、IBM Cell SDK 3.0に含まれている最適化されたEuler法のコードによる演算速度も掲載しています。XLC SSC版は「XLCで行こう!第三回」 に掲載されているOpenMP最適化を施したバージョンを使っています。また、 IBM SDK版は、ppu-gccとspu-gccによる一般的なCell/B.E.プログラミングコードで、SIMD化、ループアンローリング等、Cell/B.E.ではおなじみの最適化手法を適用済みのものです。ちなみに、今回の性能評価でSPEを4つにしている理由は、IBM SDK 版のEuler コードでSPEの数が4の倍数である必要があったためです。

結果として、XLC 版の約1/2、IBM SDK版の2倍ということで、ちょうど中間的な性能結果となりましたが、スカラーコードからのコードの追加行数はXLC版と大差ありませんので、大健闘と言ってよいと思います。ただし、先にも述べたとおりtaskに割り当てるデータのサイズをプログラマーが指定する必要がある点には不満が残ります。図 2(左)を見ていただければわかるように、パフォーマンスの変化は配列のサイズに対してそれほど敏感ではないようですし、最高のパフォーマンスを得るためにデータ分割の粒度を自分で決めたい場面もあると思うので、手動でも問題ないかも知れませんが、例えばIntel Threading Building Blocks のように、データ分割の粒度について自動と手動を切り替えられると使い勝手が良くなると思います。今後のバージョンで改善を期待したいところです。


図2(左): 各並列化手法による処理時間の比較 (右): CellSs ソースコードにおいて、ブロックサイズの変化に対する処理時間の変化
性能評価結果

まとめ

今回の記事では、CellSsをインストールし、簡単なプログラムを作成しました。結果として、 Euler粒子シミュレーションのコードではXLC SSCよりも効率の良いコードを生成することができました。

次回の「CellSsはどうだい?」第2回では、より詳しくCellSsの特性を調べていきたいと思います。



参考文献

学ぶために

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

議論するために
  • IBMのエキスパートやdeveloperWorksの実践者達から素早い回答を得るには Cell Broadband Engine discussion forumに参加してください。

  • The Cell Broadband Engine/Power Architecture notebook はブログベースの情報サイトで、ニュースや、2つの教育的な資料集: フォーラムでの面白い質問や議論の熱いトピックスをまとめた"Forum watch"や、"Infobomb" シリーズ (Cell/B.E.ドキュメント群から、短くて、正確で、特定のタスクに注目できて、斜め読みに適した"ボム"が集められている) が利用できます。


著者について

浅原 明広の肖像

浅原明広は、Cell/B.E.の発表当初から関連の開発業務に携わってきました。しばらくProject / Resource マネージメントを主な業務としていたため、実際に手を動かす作業から遠ざかっていましたが、最近現場復帰しまして苦しくも楽しいコーディングを堪能しております。ちなみに文章とは関係ありませんが、写真はナノケア中のものです。




記事の評価


サイト改善のため、ご意見をお寄せください。こちらのフォームからお願いいたします。



 


 


不充分・不完全である大変素晴らしい
 


この記事を共有する

del.icio.us del.icio.us newsing newsing FC2ブックマーク FC2ブックマーク
Choix! Choix! ニフティクリップ ニフティクリップ Yahoo!ブックマーク Yahoo!ブックマーク
MM/memo MM/memo CZブックマーク CZブックマーク livedoorクリップ livedoorクリップ
はてなブックマーク はてなブックマーク Buzzurl(バザール) Buzzurl(バザール)




上に戻る


IBM is a trademark of IBM Corporation in the United States, other countries, or both. Linux is a registered trademark of Linus Torvalds in the United States, other countries, or both. Other company, product, or service names may be trademarks or service marks of others. 他の会社名、製品名およびサービス名等はそれぞれ各社の商標です。

    日本IBMについて プライバシー お問い合わせ