Visual Studio 2015におけるMS-MPIのデバッグ実行

はじめに

Visual StudioとMS-MPIを使った並列実行アプリ開発において、Visual Studio上でデバッグ実行を行う方法を紹介します。 Visual Studio2015に標準搭載されている「プロセスにアタッチ」機能を使います。 これは元々は、DLLなどのライブラリをデバッグするための機能ですが、これをMPI並列実行中のプロセスにアタッチすることで、デバッグ実行を可能にします。

モチベーション

MPI並列アプリケーションをVisual Studioを使って開発する場合、Windows環境ということもあり、Microsoft製のMS-MPIライブラリを利用してきました。 しかし、Visual Studio 2012以降、一点だけ不便なのが、MPI並列ではデバッグ実行が使えないということです。 ここでいうデバッグ実行とは、下の図の様に、F9キーなどでコード中の任意の位置に指定したブレークポイントのところでアプリ実行中に一時中断し(黄色の行)、変数の中に入っている値などが表示できる機能です。 ブレークポイントで停止した後は、F10キーでコードを一行ずつ実行しながら進めていくことが可能で、その変数の値がどのように変わったかも確認できます見られます。 このデバッグ実行があれば、昔ながらのデバッグライトを使わずに済みます。Visual Studioの最も便利な機能の一つでしょう。私自身も15のころからこのデバッグ実行には大変お世話になっており、研究用途やスパコン向けのアプリを作る現在でも デバッグ実行欲しさにVisual StudioとC++を使ってローカルマシンで開発しております。

しかしながら、MPI並列実行時にはこのデバッグ実行ができませんでした。 なぜなら、Visual StudioからMPI並列実行をする際には、実行ファイルの指定の部分を $(TargetPath)からmpiexec.exeに変更し、$(TargetPath)は引数に指定する必要があったためです。 そのため、Visual Studioの標準デバッガ(ローカルデバッガ)がアタッチを試すのはmpiexec.exeであって、 $(TargetPath)に指定された目的のアプリではないのです。 Visual Studio 2008まではMPI debuggerなるプラグインがあって、MPI並列でもデバッグ実行が可能だったのですが、 Visual Studio 2012以降はこの問題によってデバッグ実行ができません。

一方で、MPI開発時のデバッグは、非常に大変です。 デバッグライト(printf文などで情報をstdout等に書き出し)を使った方法が一般的なのだと思いますが、 MPIではこれが満足に動きません。なぜなら、

というような問題が経験上起こります。 デバッグライトのprintf文の直後にfflush(NULL)を明記してある程度の対策はできますが、それでも完全ではありません。 なによりも、どの変数の値がどのタイミングでおかしくなっているのか見当がつかない場合には(見当を付ける過程では)、 デバッグライトはあまりにも非効率です。 なんとかデバッグ実行をできないものかと考えて、見つけたのが「プロセスへのアタッチ」機能です。


このメモ時点での環境。すでにVisual Studioはインストール済みの環境を想定しています。
  1. OS:Windows 10 pro. 64bit
  2. C++コンパイラ:Visual Studio 2015 community
  3. MPIライブラリ:MS-MPIv7

プロセスへのアタッチ

まず、Visual Studioの設定は前回の記事と同様にして、MPI並列実行できるようにします。 アクティブソリューションはDebugにしておましょう。 コンパイルしたら、「メニュー」→「デバッグ」→「デバッグの開始」、もしくは、ショートカットキーで「F5」で実行します。 そうしたら、実行中の状態で「メニュー」→「デバッグ」→「プロセスにアタッチ」を選択します。 すると、次の図のようなダイアログが現れます。

ダイアログに表示されるリストの中に、実行中のアプリがあるはずです。 今回の例ではアプリ名は"mpi_pseudo_debugger.exe"です。ちょうど選択中(青ライン)のものですが、その下にも同名のプロセスがあります。 これは、今回はmpiexecの引数に、

"-np 2 $(TargetPath)"
と指定して2MPI並列実行しているため、プロセスも2つ現れます。 4MPI並列で実行したらしたら4つのプロセスが現れます。 このうちの一つを選択し、ダイアログの「アタッチ」ボタンを押します。 これでアタッチされ、以後そのプロセスに限り、ブレークポイントでも静止しますし、マウスカーソルを合わせれば変数の値も見れます。

アタッチするプロセスはルートプロセス(rank==0)でなくてもいいと思います。むしろ、どれがルートでどれがサブプロセスか見分けるのが大変かもしれませんね。ダイアログに表示されるID順とも限らないかもしれないので。

アタッチしやすい工夫

さて、プロセスにアタッチは実行中にしかできません。よって計算がブレークポイントを通過する前に、急いでアタッチしなければいけません。 しかも手動で。これはさすがに大変なので、コードに工夫をしました。 最初に示した図のコードがまんまサンプルなのですが、 MPI_Init直後に、getchar()関数を呼ぶことで強制的に待機させます。この時にルートプロセスだけgetchar()のEnterキー入力待ちにさせて、残りのプロセスは次のMPI_Barrierで待機させてます。 こうすれば、開始直後に全てのプロセスが待機状態になるので、ゆっくりと「プロセスにアタッチ」してもらってから、コンソールに戻ってEnterキーを押してプロセスを再開してください。

余談

こういう使い方に既に気づいている方もいるのだと思うので、えらそうにここで解説するのも本当は恐縮ですが、MS-MPIで検索して私のホームページに来られる方も多少おられるようなので、情報共有の意味で紹介しました。

いやしかし、一年前に気付いていればあのライブラリは絶対にC++で開発していたなぁと、、、