Previous ToC Next

30. 並列計算機のプログラミングモデル (2006/10/1)

すでに何度も繰り返したように、現在スーパーコンピューターといえるような 速い計算機を作るには、なんらかの方法で並列処理する計算機を作るしかあり ません。歴史的には、並列計算機には色々な種類がありますが、使い方、つま りソフトウェアの観点から重要なものは分散メモリか共有メモリか、というも のです。

分散メモリの計算機とは、要するに普通の PC をイーサネットでつないだよう なもののことです。これをどうやって使うかというと、普通は MPI のような いわゆるメッセージパッシングによるプログラミングモデルで使うことになり ます。

メッセージパッシングというのはどういうモデルかというと、なんということ はなくてそれぞれの計算機ではバラバラにプログラムが勝手に走っていて、メ モリ空間も自分のメモリしか見えないのですが、番号を指定して他の計算機に データを送ったり、送られれてきたデータを受け取ったりすることができる、 というものです。

単純な例として、オープンソースの MPI 実装である MPICH についてくるサン プルプログラムを少し整理したものをみてみましょう。

 #include "mpi.h"
 #include <stdio.h>
 #include <math.h>
 
 double f(double);
 
 double f(double a)
 {
     return (4.0 / (1.0 + a*a));
 }
 
 int main(int argc,char *argv[])
 {
     int done = 0, n, myid, numprocs, i;
     double PI25DT = 3.141592653589793238462643;
     double mypi, pi, h, sum, x;
     double startwtime = 0.0, endwtime;
     int  namelen;
     char processor_name[MPI_MAX_PROCESSOR_NAME];
 
     MPI_Init(&argc,&argv);
     MPI_Comm_size(MPI_COMM_WORLD,&numprocs);
     MPI_Comm_rank(MPI_COMM_WORLD,&myid);
     MPI_Get_processor_name(processor_name,&namelen);
 
     fprintf(stdout,"Process %d of %d on %s\n",
     myid, numprocs, processor_name);
 
     if (myid == 0) {
 n=100000000; 
 startwtime = MPI_Wtime();
         MPI_Bcast(&n, 1, MPI_INT, 0, MPI_COMM_WORLD);
     }
     h   = 1.0 / (double) n;
     sum = 0.0;
     for (i = myid + 1; i <= n; i += numprocs){
 x = h * ((double)i - 0.5);
 sum += f(x);
     }
     mypi = h * sum;
     MPI_Reduce(&mypi, &pi, 1, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD);
     if (myid == 0) {
 printf("pi is approximately %.16f, Error is %.16f\n",
        pi, fabs(pi - PI25DT));
 endwtime = MPI_Wtime();
 printf("wall clock time = %f\n", endwtime-startwtime);       
 fflush( stdout );
     }
     MPI_Finalize();
     return 0;
 }
これから、 MPI に関係する部分を取り除いて1台の計算機で走るプログラムに したら以下のようになります。

 #include <stdio.h>
 #include <math.h>
 
 double f(double);
 
 double f(double a)
 {
     return (4.0 / (1.0 + a*a));
 }
 
 int main(int argc,char *argv[])
 {
     int  n,  i;
     double PI25DT = 3.141592653589793238462643;
     double  pi, h, sum, x;
     double startwtime = 0.0, endwtime;
     n=100000000; 
     h   = 1.0 / (double) n;
     sum = 0.0;
     for (i = 0; i < n; i ++){
 x = h * ((double)i - 0.5);
 sum += f(x);
     }
     pi = h * sum;
     printf("pi is approximately %.16f, Error is %.16f\n",
    pi, fabs(pi - PI25DT));
     return 0;
 }
中身の説明はここではしませんが、長さがほぼ半分になっているのがわかると 思います。 MPI 版のほうでは、最初に MPI 関係の初期設定のために色々な関 数を呼び出し、さらに計算処理が、自分の番号が何番であるかをみてどういう 計算をするべきか各計算機が判断し、最後に各計算機が求めた結果を合計する ための MPI 関数を呼び出し、番号 0 の計算機が結果を出力する、という具合 になります。 MPI では、このように、各計算機が何をするべきかを全てアプ リケーションプログラムを書く人が指定することになります。

これに対して、共有メモリの計算機とは、例えば 2CPU の Intel や AMD のサー バーや、あるいは現在ではデュアルコアの CPU のような、複数の CPU が単一 のメモリを物理的に共有しているものです。ここでどのようなプログラムを書 くかは色々なのですが、例えば最近普及してきた OpenMP なら、理屈では

 #pragma omp parallel for reduction (+:sum)
という1行を for ループの前に書くだけでいいはずです(試してないので駄目 だったらすみません)。古典的なベクトル計算機の場合でも、基本的には同様 にコンパイラがベクトル命令を使うかどうか判断してくれます。

実は、このサンプルプログラムではあまり違いがわからないのですが、普通の プログラムではもっと根本的なところで差がでてきます。つまり、例えば流体 計算で3次元空間を3次元配列で表す、という場合に、並列化していないプログ ラムなら単純に1つ配列をきればよいですし、 OpenMP のプログラムでも同様 です。ところが、 MPI では計算したい領域をさらにプロセッサ数で分割し、 それぞれのプロセッサが小さい配列をもつことになります。で、計算力に自分 がもっていない隣のデータが必要になりますが、それは MPI の関数を使った 通信でもらってくるわけです。従って、配列宣言自体が変わるし、また隣のデー タをもらってくるとか、それに対応して隣にデータを送るとか、そのための準 備とか後始末とかいった処理が一杯発生します。まあ、それらは単に面倒なだ けともいえるのですが、問題はこれまでの並列化されていない、あるいはベク トル計算機では動いていたプログラムを少しづつ変更していって動くように する、というわけにはいかないことです。

これは、単純にはデータ構造が変わるから、ということになります。これまで、 1つのプログラムからデータ全体が見えていたのに、 MPI でとした時点でそう ではなくなります。このために、プログラムの殆ど全ての箇所でなんらかの変 更が必要になるのです。この違いのために、 MPI を使って今までにあるプロ グラムを並列化できるように書き換える、というのはなかなか困難で、多くの 場合に始めから新しく書いたほうが早い、ということになります。ところが、 そうはいってもこれまで何十年もかけて開発されてきた、ノウハウの塊のよう なプログラムが使われているわけで、それと同じように動くものを新しく作る、 というのは不可能ではなくてもとてもとても大変な作業になります。

このようなわけで、原理的には分散メモリの並列計算機で実行することがそれ ほど難しいことではないと思われるようないろんなアプリケーションで、現実 問題としては既にベクトル計算機や普通の共有メモリ計算機用に書かれた大規 模なプログラムを移植することが実際には不可能に近い、という状況になって います。もちろん、この状況をなんとかしようという研究開発は昔から一杯あ ります。そのなかで、ある程度使われた/使われているものの一つは CM-Fortran, VPP-Fortran, HPF (High-Performance Fortran) といった、配列 を自動的に分散配置して、演算も並列実行してくれるようなシステムです。

もうひとつのアプローチは分散共有メモリと呼ばれるもので、物理的には分散 メモリの計算機なんだけれど、ハードウェア、あるいはソフトウェアの仕掛け をつけることで並列計算する計算機全体でグローバルなメモリ空間を論理的に 用意しようというものです。これは、スタンフォード大学のグループ等が90年 代に色々研究しており、 SGI から商品化されたものもでています。これはハー ドウェアで論理的な共有メモリを実現するものです。ハードウェアの場合は通 常2次キャッシュのキャッシュラインが共有の単位になります。つまり、具体 的には 32 バイトとか64バイトとかいった単位で、自分のメインメモリにない ものにアクセスがあるとそれを他のプロセッサにもらいにいって、キャッシュ に記憶するわけです。 ソフトウェアの場合には通信の単位がキャッシュライ ンでは小さ過ぎるので、ページ単位、つまり 4k バイトとか 8k バイト程度を 単位に通信されます。

HPF のようなシステムでも分散共有メモリでも、重要なことはとりあえず今ま であるプログラムを一から書き直さなくても(速度がでるとは限らないですが) 実行は可能である、ということです。このために、前節で書いた

  「一度には一ヶ所しか変更しない」
という原則を守る形でプログラムを作ることが可能なのです。これは MPI で のプログラム開発との根本的な違いです。

分散共有メモリの利点は、コンパイラが通信の面倒を見る必要がないのでシス テムを作ること自体は簡単である、ということですが、欠点はなかなか性能が でないということです。つまり、キャッシュラインとかページとかを単位に通 信を行うし、またそれが必要になったところでリクエストがでるので、例えば 配列の処理を始める前にあらかじめ隣のプロセッサのデータのうち必要な部分 だけをもらってくる、ということが効率良くできるわけではないのです。もち ろん、色々やって効率良くすることはできますが、それは結局CM-Fortran で やっているような通信をソフトウェアで実現する、というのに近付いていくこ とになります。

しかしながら、 CM-Fortran, VPP-Fortran はそれぞれ今はなき TMC CM-2/5 や富士通の VPP でしか実行できないわけで、そういった計算機がなくなった ら困るわけです。というか、これは現在現実にいろんな人が困っています。 もっとも、これらから HPF への移行はそれほど手間ではないので、 HPF が動 作する機械への移行は可能です。ところが、 2006年現在では、 HPF で書いた プログラムでちゃんと性能がでる機械は事実上 NEC の SX だけ、という感じ になっています。大きな問題は、 MPICH のようなオープンソースの実装が存 在せず、 NEC、富士通といった計算機メーカーと、PGIのようなコンパイラメー カーの実装しかないことです。NEC や富士通はもちろん自分のところの計算機 で動くことが重要なので、他の計算機で動くものを作ってはくれません。 PGI は競争がないのでなかなか性能が上がってこない、というのが過去 10年くら い続いていてなかなか進歩がありません。

現状では、 NEC SX のような分散メモリベクトル並列計算機は PC クラスタに 比べて非常に価格性能比が悪い、というのは何度も何度も書いてきたことです が、その理由の一つは HPF コンパイラが出す必ずしも非常に高度に最適化さ れていない通信パターンでも計算に対して通信が間に合う程度に高速な通信ネッ トワークをつけているから、いうことはあります。 MPI で書き直す時には 使うほうが余計な通信はしないしなるべくまとめて通信するように書くわけで すが、コンパイラによる自動変換ではなかなかそうはいかないわけです。

つまり、現在では、安くて速い並列計算機は PC クラスタという形で構築可能 だけれど、その上でちゃんと動くプログラムを作るためには MPI で開発する 必要があり、これまでのプログラムがそのままでは使えないし余計なことを一 杯書かないといけないので開発効率が極めて低い、ということになります。こ れに対して NEC SX のようなベクトル並列計算機では HPF のような MPI より ははるかにましな開発環境が使え、従来のプログラムとの互換性を保った形で 開発することも可能なのですが、ハードウェアの価格性能比が極めて悪い、と いうことになっているわけです。

もちろん、3体問題を沢山やる、といった処理なら、 PC クラスタのほうがむ しろ簡単で効率がでる、ということになるのですが、そうではなくて本当に1 つの問題を並列化して早く計算したい、という場合だとそれではすまなくて 大きなデータを分散メモリなり共有メモリなりでなんとかする必要がでてくる わけです。

実は、もうちょっとややこしい問題があります。それは、 HPF のような明示 的に並列処理を記述する言語で、プロセッサ数に独立に動作するように書かれ たプログラムは、原理的にキャッシュの再利用等が起こりにくい形に書かれる ことになるのでベクトル機のようなメインメモリのバンド幅が演算速度に対し て十分にあるアーキテクチャでないと性能がでないのですが、 MPI で書いて しまえば通信部分以外のプログラムは1つの CPU で普通に走っているのでいか なるチューニングでもできる、ということです。これは、スーパーコンピュー タを使う目的の大きなものは速く計算することであるのに、 HPF のような言 語を使ってかいたプログラムではメモリ階層が深い計算機で速くするためのチュー ニングが困難であるということを意味しています。

おそらくはこのことが理由となって、アメリカでは HPF はあまり人気がなく、 その代わりに Co-Array Fortran という MPI のようにプロセッサが自分のロー カルなデータをもつけれど、他のプロセッサのデータを MPI よりはわかりや すい形でアクセスできる言語がある程度使われるようになってきています。こ れはもともと Cray T3x 上で開発された言語であり、比較的速いネットワーク があるので通信をある程度適当にやってもいいけれど、プロセッサの主記憶は 計算より遅いので1プロセッサの中でのキャッシュへの最適化は非常に重要で ある、という環境に適した言語になっています。

これは結構本質的な問題で、大規模計算のためのソフトウェア開発をどのよう な形で行うべきか、という原理的な問題であるともいえます。性能がでなけれ ば意味がない、というのが必要条件としてあった上で、計算機アーキテクチャ が変わる毎に始めから全部プログラムを書き直すわけにはいかないという条件 がさらにつくわけです。

この時、全体を MPI で書く、というのは現時点では現実的な選択肢であるこ とはもちろんです。当面、 MPI が動かないような並列計算機というのも考え にくいから、これで書いておけばプログラム全体としての構造は計算機が変わっ ても使い回すことができ、しかも各ノード計算機では普通のプログラムで普通 にコンパイルされるので、その中でアーキテクチャに特化した最適化とかチュー ニングはできることになります。

しかしながら、 MPI は極めて制限が多く、機能も少なくて書きにくい開発環 境であるのは疑いないところであり、もうちょっとまともな何かが必要なこと は間違いありません。これは、重要な課題であることは確かだと思います。し かし、現実に PC クラスタ等では MPI 以外に現実的な解は存在しません。

富士通 VPP や NEC SX、特に SX-6 以降のマシンのような、比較的小規模なノー ドからなる分散メモリシステムは、 VPP-Fortran や HPF のようなユーザー からみて非常に生産性が高いプログラミング環境をサポートしてきたわけで、 そのことの意義はもっとちゃんと評価される必要があります。

とはいえ、どう評価してどうするべきか、は難しい問題です。

一つの考え方は、実際にそのような生産性を実現してきており、そのために例 えば地球シミュレータでも完成して比較的短い時間でちゃんと大規模計算に使 えるようになったわけですから、今後も是非ともそういうソフトウェア環境が 使えるハードウェアの開発をつづけるべきである、というものです。

これはまあ理屈は通っていなくもないのですが、ではハードウェアのコストが 100倍になってもいいのか?と言われるとそれはちょっと違うのではないかと 思います。

理想的には、十分に良い価格性能比がある、例えば PC クラスタと比べて優位 に競争できるハードウェアで、しかも富士通 VPP や NEC SXであったようなそ れなりに使いやすいプログラミング環境をサポートすればいいわけでです。

これはでは不可能か?ということを考えてみましょう。
Previous ToC Next