Hatena::ブログ(Diary)

ちくたく このページをアンテナに追加

2012-04-04

OpenMPIでダイナミックな環境を構築

MPIを使ってスパコンのような並列コンピューティング環境を構築するにあたって、一番、気になった点は、「どうやってそれぞれのマシンで実行プログラムを共有するか?」である。

学校や企業でスパコンのような環境が用意されている人たちはあまり気にしなくてもなんらかの運用方法が提供されているだろうが、一から環境を組み上げる人にとっては気になる点だと思う。

Webで調べてみると、どうやらNFSで実行プログラムを共有するのが一般的なやり方のようだ。全てのコンピュータNFSでマウントされたファイルを読み、そこから実行プログラムを取り出す。うーん、趣味に合わない。スパコンってダイナミックでヘテロ*1な環境で動くんじゃないの?というのが私の主張。世の中、NFSが主流のようだが、なんだかしっくりこない、というわけでいろいろ調べてみたので、その結果を残しておく。

実行プログラムの共有方法

実行プログラムの共有方法はいくつかある。

  1. 実行プログラムNFSでマウントして、複数コンピュータで使う
  2. 実行プログラムをそれぞれのマシンに転送(scp,ftp,etc)して使う
  3. 実行プログラムをそれぞれのマシンでコンパイルして使う
  4. 実行プログラムを実行時にそれぞれのマシンに自動転送して使う

一般的なのが1、2。3は面倒だが、ヘテロなマシン(Linux,Mac,Win,etc)で環境を構築できる。そして、私がしたいのは4。

OpenMPI

MPIにはMPICH2、LAM、OpenMPIといった様々な実装がある。私は、Macに初めから搭載されていたOpenMPIを使って見ることにした。

まずは、OpenMPIで簡単なプログラムを作って、動作させる。動作方法は上記2と3。どちらもうまく動くことを確認。

次に4にトライ。OpenMPIのmpirunコマンドのマニュアルを読むと、"--preload-binary"というオプションがある。まさしく、私が求めていたもの。

実行プログラムの自動転送

"--preload-binary"動かしてみる。

% mpirun -host 192.168.1.1 --preload-binary -np 10 ./a.out

以下のエラーメッセージが出て、動かない。

--------------------------------------------------------------------------

WARNING: Remote peer (26776,0],1]) failed to preload a file.

Exit Status: 256

Local File: /tmp/openmpi-sessions-xxx@localhost.localdomain_0/26776/0/a.out

Remote File: a.out

Command:

scp host2:/home/hoge/a.out /tmp/openmpi-sessions-hoge@localhost.localdomain_0/26776/0/a.out

Will continue attempting to launch the process(es).

--------------------------------------------------------------------------

--------------------------------------------------------------------------

mpirun was unable to launch the specified application as it could not access

or execute an executable

--------------------------------------------------------------------------

scp時にパスワードを手動設定するところに問題があるようなので、以下のページを参考にして、マシン間の双方向でsshログインを自動化。

http://www.turbolinux.com/support/document/knowledge/152.html

それでも変わらないエラーメッセージ

しかたなく、ググッてみると、以下のページに出会う。

http://www.open-mpi.org/community/lists/users/2009/12/11479.php

要点をまとめると以下。

  • OpenMPIの1.3とか1.4では"--preload-binary"の動作にバグがある可能性がある
  • sshの自動ログインは双方向でできるようにしておく必要がある

なるほど。そういうことか・・・

って、バグかよ!

と疑いたくなるところをぐっとこらえて、もう一度エラーメッセージを見てみる。

WARNING: Remote peer (26776,0],1]) failed to preload a file.

Exit Status: 256

Local File: /tmp/openmpi-sessions-xxx@localhost.localdomain_0/26776/0/a.out

Remote File: a.out

Command:

scp host2:/home/hoge/a.out /tmp/openmpi-sessions-hoge@localhost.localdomain_0/26776/0/a.out

Will continue attempting to launch the process(es).

これは、ファイルのコピー(scp)に失敗していることを意味する。もしかしたら、この時点でsshの双方向ログインがうまくいっていない。いつもは、IP直打ちならうまくいくことを確認していたが、このメッセージ内ではホスト名が使われている。

以下のコマンドを打ち込んでみる。


scp host2:/home/hoge/a.out /tmp/openmpi-sessions-hoge@localhost.localdomain_0/26776/0/a.out

パスワードを入力するよう確認してきた・・・

ホスト名を使った場合、双方向通信ができていない。

ということで、sshで自動ログインできるようホスト名で再設定。もちろん、双方向ができるように設定。

そして、再度、トライ・・・・

やった、できた!

最後に

バグかと疑ったが、自分が間違えていたい。よくあることだ。反省反省。

ちなみに、1.5でもうまく動きました。

私の考えるダイナミックな並列コンピューティング環境*2に一歩近づいた。

*1:調べてみると、同一CPUアーキしかサポートしてない

*2sshの自動ログイン設定はしないといけないが・・・

2012-03-29

Handlerクラスの正しい使い方(Androidでスレッド間通信)

AndroidのHandlerクラスは別スレッドからUI部品操作を用いる際に、よく使われる。Androidの場合はUIスレッドからでないとUI部品を操作できないという制約がある。どのサイトを見てもUIスレッドへイベントを送るための仕組みとして語られている。

いやいや、それは事実だが、それだけでない。

Handlerクラスはスレッド間通信のための仕組みである。もっと正確に言うと、Handlerインスタンスを生成したスレッドへイベントを送るための仕組みなのである。当たり前だと思う人も多いかもしれないが、多くの人はこのことを理解できていない。

ソースレベルで説明してみる。よく書かれるソースは以下のような感じである。

Handler handler = new Handler(); // (1)
handler.post(new Runnable() {
	@Override
	public void run() {
		// UI部品への操作;
		return;
	}
});

このソースのポイントは(1)である。(1)を実行したスレッドへHandler.post()で指定したRunnableが送られる。つまり、UIスレッドで(1)を実行すれば、UIスレッドへ送られ、別スレッドで(1)を実行すれば、別スレッドへ送られる。

では、その実体をAndroidのソースHandlerのコンストラクタのソースを見て、確認してみる。

    public Handler() {
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }

        mLooper = Looper.myLooper(); // ポイント(1)
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue; // ポイント(2)
        mCallback = null;
    }

ポイントは2点。ポイント(1)で呼び出しスレッドの宛先(Looper)を取り出し、ポイント(2)で宛先のポスト(mQueue)へRunnableを送りつけている。このソースを見るとよくわかるのだが、Handlerクラスは決してUIスレッドへRunnableを送りつけるためのものではない。Handlerインスタンスを生成したスレッドへイベントを送りつけるための仕組みである。

Runnableを送りつけたい宛先を明示したい場合は、もう一つのコンストラクタであるHandler(Looper looper)を使う。これを使えば、任意のThreadへRunnableを送りつけることができる。

この仕組みさえ知っていれば、任意のスレッドとの通信が可能となる。例えば、WebViewを使っている場合は、WebViewの本体であるWebViewCoreThreadと通信することが可能となる。

例えば、こんな感じ。

public class HogeTask extends Thread {
	public HogeTask() {
	}
	
	public void execute(Looper toLooper, String data) {

		// ここでスレッドでしたい処理を記述

		new Handler(toLooper).post(new Runnable() {
			public void run() {

				// ここで宛先toLooperでしたい処理を記述

				return;
			}
		});
	}
}

呼び出し側はこんな感じかな。

new HogeTask().execute(toLooper, "hogehage");

toLooperには宛先スレッドのLooperを入れるとよい。Looperの取得の仕方は、対象となるスレッド上でLooper.myLooper()とすればよい。ちなみにUIスレッドのLooperを取得したい場合は、Looper.getMainLooper()を呼べばよい。

これで、HandlerとLooperの神髄を理解することができたと思う。これだけの知識があれば、スレッド間通信はお手の物のはずだ。