並列計算の面白さ

ロボ太
·

私は数値計算で科学研究を行う、「数値計算屋」と呼ばれる種類の研究者です。特に、スーパーコンピュータを使った並列計算を専門としています。並列計算とは、長時間かかる計算を複数のコアを使って早く結果を得たり、一台だけのPCではできないような大規模な計算をしたりすることです。その意味で並列計算はあくまでも手段であって目的ではないのですが、私は並列計算そのものが面白いと思っています。ここでは、並列計算のどこが面白いのか、簡単に紹介してみようと思います。

一口に並列処理といっても、様々な種類があります。たとえば今これを見るのに使っているであろうブラウザも、複数のプロセスが様々な仕事を分担しています。これも一種の並列処理です。ブラウザは、複数のプロセスがそれぞれ異なる仕事を担当していますが、数値計算における並列計算は「複数の計算資源を同時に使って、一つのタスクをこなす」ことを指すことが多いです。

並列計算は、大きく分けて3つのレイヤーがあります。まず一番上(どっちが上かは議論がありそうですが)がプロセス並列です。プロセスというのは、オペレーティングシステム(OS)から見た処理の単位で、それぞれ独自のメモリ空間を持っています。ブラウザでネットを見ているとき、タスクマネージャなどで見てみると、複数のプロセスが走っているのがわかると思います。これもプロセス並列です。プロセスは、物理的に別の場所にある計算機と通信を行うことができます。これにより、複数のPCを同時に利用した並列計算が可能になります。現代のスーパーコンピュータは「ノード」と呼ばれるPCのようなものが複数集まってできており、それぞれのノードはそれぞれOSが走っています。それらが協調して動作するためにはプロセス並列と通信が必須となります。

プロセスの下にあるのがスレッドです。スレッドとは、簡単に言えばCPUコアを使いにいく単位です。現代のCPUはほとんどマルチコアになっており、並列処理が前提で作られています。各プロセスは一つ以上のスレッドを持っています。プロセス一つの下に複数のスレッドがぶら下がった状態で何か並列処理をするのがスレッド並列です。メモリ空間はプロセス単位なので、スレッド並列はメモリ空間を共有する、共有メモリ並列となります。それに対してプロセス並列は分散メモリ並列となります。

プロセス並列、スレッド並列はわりと抽象的な概念ですが、直接ハードウェアが「見える」並列手段としてSIMDがあります。SIMDはデータ並列と呼ばれるレイヤです。昔のCPUは動作周波数を向上させることで性能が上がってきました。しかし、発熱の問題から動作周波数は2~3GHz程度で頭打ちになってしまいました。また、パイプライニングという仕組みで、1サイクルに1つ計算ができるようになりました。これ以上の性能向上のためには、1サイクルに複数の計算をさせるしかありません。これがSIMDです。SIMDはSIMDレジスタと呼ばれる幅の広いレジスタに複数のデータを載せて、一度に複数の演算を一度に行う仕組みです。どんな幅のレジスタが何本あるか、どのような演算をサポートしているかはCPUによって異なるため、直接ハードウェアが見えることになります。

さて、数値計算屋が「並列化するか」と思ったら、まずはスレッド並列から入ることが多いと思います。数値計算では大きなforループを扱うことが多いため、たとえば「1000回転するループを、10スレッドで分担したらそれぞれ100回転で済むから早くなるよね」みたいなことをします。多くの場合、OpenMPという仕組みを使って、並列化したいループの直前に特殊なコマンドを書くことで、コンパイラに「このループをスレッド並列化したいよ」と伝えます。するとコンパイラはよしなに並列化してくれます。しかし、単純なループなら良いですが、ループの中身が複雑になってくるとコンパイラが並列化できなくなったり、性能が全くでなかったりします。すると自分でスレッド並列を書くしかありませんが、ここで「共有メモリである」ということが問題になります。各スレッドでメモリを共有しているということは、必要なデータが全部見えているということでもありますが、自分が必要なデータを他のスレッドがいつ書き換えるかわからない、ということでもあります。そこで「欲しいデータが、欲しい時に確実に使える状態になっている」ことをプログラマが保証してやる必要があります。そのために排他制御を書いて「このメモリを触れるのは一度に一つのスレッドのみ」となどとしますが、下手な書き方をすると順番待ちが生じてしまい、性能が出なくなります。最低限の排他制御で、必要なデータが必要なときに使えるようにするのがプログラマの腕の見せ所です。一般にマルチスレッドプログラムは開発もデバッグも非常に大変ですが、うまくハマった時、パズルがきれいに解けたような快感を感じることでしょう。

スパコンは、複数のノードを協調動作させて使います。スレッド並列ではノードをまたぐことができないので、スパコンを使うためにはプロセス並列が必須です。そして、各プロセスは独立なメモリ空間を持っているので、協調動作をさせるためには通信が必要になります。今でこそインターネットが当たり前の世界になっていますが、昔は通信と言えば電話回線を使ったものくらいしかありませんでした。Windows95が出て、Windows用のフリーソフトが多数公開されるようになっても、ダウンロードが大変だから、フリーソフトを多数集めたCD-ROMがついた雑誌がよく発売されていたものです。そんな、まだ常時接続が当たり前ではない頃、大学の研究室で初めて通信コードを書いてみて、同じ部屋のあるPCから別のPCにメッセージが送られたのを確認した時、すごく感動したのを覚えています。

時は流れ、私は神戸にあった京コンピュータというスパコンを使う機会がありました。京コンピュータは8万ノードからなる大きなスパコンで、広い部屋にたくさんのラックが整然と並べられていました。その広い部屋に置かれたコンピュータを全て占有するジョブが走った時、大量の脳汁が出たのを今でも思い出します。

並列計算は、複数のノードが協調して一つのタスクをこなすものです。数値シミュレーションでは、ある一つのシミュレーションボックスの計算が、多数のノードの上で走ることになります。そのシミュレーションボックスの中にはたくさんの原子がありますが、この原子は「今、自分がどのノードのどのコアで計算されているか」を知りません。さらに、計算の途中で、その原子の情報を持つノードが変わるかもしれません。でも、その原子の軌道はあくまでも一つの運動方程式の解であり、自分がどこで計算され、どのノードのメモリにいたかなどは関係がなく、意識することもないでしょう。これも並列計算の面白いところだと思います。

この世界が仮想空間であり、自分がAIかもしれない、と想像したことがあるでしょう。その仮想空間は、巨大なスパコン上でシミュレーションされているかもしれません。あなたという意識は、複数のノードにまたがって存在し、しかも常に同じノード上にあるとは限らないでしょう。あなたの意識は複数のハードウェアに遍在しており、でもあなたは自分が「一つの個」と認識しているでしょう。大規模並列計算で一つの世界をシミュレーションする時、そんなことが起きています。

並列数値計算では、一つの世界が複数の計算機の上でシミュレーションされています。ハードの上では複数のプロセスがお互いに通信し、複数のスレッドが好き勝手にメモリを書き換えながら、全体として何か一つの世界を表現しています。並列計算は開発もデバッグも大変ですが、うまくいった時には、滅茶苦茶でかくて複雑なピタゴラ装置が成功したような、そんな楽しさがあります。

ここではSIMDの楽しさについては触れませんでしたが、やはりパズルのようであり、さらに「生のハードを触っている感」があって、これも面白いです。

科学技術計算において、並列計算は手段であって目的ではありません。しかし、並列化そのものが面白いので、まずは性能とか気にせずにやってみると良いと思います。ちょっとスレッド並列やってみて、10スレッド使ったコードが200倍以上「遅く」なったのを見てゲラゲラ笑ったのを思い出します。この記事を見て、並列計算やってみようかな、という人が一人でも増えたらうれしいです。

@kaityo256
記事中に明示されていない場合、私の記事はCC-BY 4.0で、記事に含まれるソースコードはMITライセンスで公開します。 github.com/kaityo256