はじめに
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ではこれが満足に動きません。なぜなら、
- MPI並列実行された各プロセスが非同期に実行されており、デバッグライトで書き出されるメッセージの順序がプロセス間で保証されない。
- 問題のバグでエラーを起こすと、MPIの各プロセスは強制的に終了する。 この時に、デバッグライトの書き出し文が記述された後のコードでエラーが起こっていたとしても、 非同期であるstdoutへの実際の書き出しの前にプロセスが落ちてしまい、デバッグライトのメッセージが出力されないことがある。
このメモ時点での環境。すでにVisual Studioはインストール済みの環境を想定しています。
- OS:Windows 10 pro. 64bit
- C++コンパイラ:Visual Studio 2015 community
- 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++で開発していたなぁと、、、