綾小路龍之介の素人思考

メモリ食いつぶすプログラムや不規則にエラーを返すプログラムをシェルスクリプトでどうにかする

本来はメモリを食いつぶすようなプログラムは書いてはいけないと思う。でもそのようなプログラムを使用しなければいけない場合もある。たとえば、メモリが 128MB のマシンで xvfb と R の組み合わせると激しくスワップ発生して vmstat とかで見ると si と so がとんでもないことになってしまった。初めのほうはスワップがほとんど発生しないが、1 日も計算させていると xvfb が物理メモリを食いつぶして主に計算している R が激しくスワップを使うようになり結局計算に長時間かかるようになってしまう。同じ計算がスワップの発生具合で 1 時間 30 分かかる場合と 4 時間かかる場合とある。xvfb を定期的に終了させれば良いので呼び出しの粒度を細かくして R と xvfb をシェルスクリプトで呼び出すことにした。

$ cat hoge.R
#!/usr/bin/R
n1 <- as.numeric(commandArgs()[4])
n2 <- as.numeric(commandArgs()[5])
for (i in n1:n2)
{
        x <- seq(-4, 4, len = 101)
        y <- sin(x - i)
        png(sprintf("%04d.png", i))
        plot(x, y)
}
$ cat hoge.R.sh
#!/bin/bash
for n1 in `seq 199 10 300`
do
        n2=`expr $i + 1`
        xvfb-run R --vanilla --args $n1 $n2 < hoge.R
done
$ nohup nice -n 19 sh hoge.R.sh > nohup.out &

このようにして、可能な限り機能を分解しそれぞれの機能をつなぎ合わせることで大きなシステムを作ることは、エラーが起きても継続的に計算をさせたい場合にもうれしい。もちろん、戻り値チェックしてエラー判定するのが最良の手段だが、お手軽にできてエラーがあっても続けて計算を継続できるという点ではこちらのほうが優れている。また、どのような場合にエラーが起こるかわからない場合についてもこの方法は有効である。メモリが足りないだのだれかが勝手にプロセスを殺しただのと、トラップ可能なエラーとそうでないエラーがあるわけで、「とにかくエラーが起きたらおちるわけだから落ちること前提でコード書きましょう」というアプローチも悪くは無いと思う。また、とにかく早く計算をスタートさせねばならないけれど、最終的には堅牢なシステムにしたいという場合に、スタートアップとしてこのような手法をとることは計算機の死に時間を減らすためにも間違いではないと思う。

例えば、上に挙げたスクリプトにおいて、引数が 353 354 の場合に、何らかの原因で R のスクリプトがうまく走らなかったとする。この場合には R は「おちる」わけだが、R がおちたところでシェルスクリプトは落ちない。だから、其の次の 355 356 の引数について R を走らせることが出来る。もし、R 内部の for ループの範囲を広げて 1:1000 とかにした場合に 353:354 でおちると計算全体が止まり、355:1000 までの計算は再開されるまで走らないことになってしまう。このようにして、「依存関係の無い計算については粒度を細かくすることで計算機の死に時間を減らすことが出来る」と思う。

今回は粒度を 2 にしているので 2 個のパラメータのどちらかが失敗するとシェルスクリプトに戻るようになっているが、この粒度を 1 個にしてしまえばよりよくなるわけだ。ただし、R スクリプトの最初で巨大なファイルをメモリに読み込んだりすると、其の読み込みに長い時間がかかることがある。読み込み以降の計算時間と読み込み時間についてよく考えないと、粒度を細かくしたら実際に結果を返すまでの計算時間がすごく長くなってしまったということにもなりかねない。

粒度 1 の場合の読み込み時間と計算時間の比率が 9:1 で結果が返るまでの総時間が t 秒の場合に 1000 パラメータについて計算することを考えてみる。粒度 1 で 1000 パラメータについて計算すると 1000 * 0.9 * t + 1000 * 0.1 * t = 1000 * t 秒かかるが、粒度 100 で計算すると 100 * 0.9 * t + 1000 * 0.1 * t = 190 * t 秒、となる。安全な粒度 1 を選択するか、粒度 1000 で賭けに出るか、粒度 100 ぐらいでお茶を濁すか、これらは計算している人が適切に選択しなければいけない点だと思う。

粒度と総計算時間
粒度読み込み時間計算時間総時間
11000 * 0.9 * t1000 * 0.1 * t1000 * t
10100 * 0.9 * t1000 * 0.1 * t190 * t
10010 * 0.9 * t1000 * 0.1 * t109 * t
10001 * 0.9 * t1000 * 0.1 * t100.9 * t

リファレンス

  1. バッチモード - RjpWiki
  2. シェルスクリプト(Bash)

ソーシャルブックマーク

  1. はてなブックマーク
  2. Google Bookmarks
  3. del.icio.us

ChangeLog

  1. Posted: 2007-06-27T19:41:49+09:00
  2. Modified: 2007-06-27T15:18:59+09:00
  3. Generated: 2017-08-11T23:10:58+09:00