はやみずの「は」

計算機での性能測定実験と集計の手法について、あるいはただの愚痴

計算機を使った性能測定実験プログラムを実行して、集計するという処理をどこまでパターン化出来るかというのを、この数年、たまに考えている。が、なかなかこれだ、という答えにたどり着けるわけでもなく悶々としている。

このエントリは、その悶々とした気持ちをただ吐き出すためのものである。

この春でいまの研究室にきてから丸々6年になるので、この6数年ずっとデータベース研究をしていて、実験の作業として結局やっているのは、色んなプログラムの実行性能を測っては分析するということの繰り返しになる。

基本的には特定のプログラムを色々条件を変えながら何度も実行して、その結果をまとめることになる。大抵1回の実行時間は短くても数分、長い時には数時間で、それを何度もパラメタを変えて繰り返すので、結構長時間の実験になる。

当然パラメタを変えながらプログラムを繰り返し実行する部分は自動化することになる。 そのときに、自動化を実現する手段が悩ましい。

最も手軽なスタートポイントとしては、まずはシェルスクリプトのベタ書きだろう。 パラメタが複雑でない場合、適当にシェルの配列に値を書き込んでおいて、それをループで回してやればよい。 例えば次のようになる。

PARAM_LIST=(a b c d)
for param in ${PARAM_LIST[*]}; do
  ./test_program $param
done

このくらいはまあ簡単で、悩みようがない。

しかし、単にプログラムを実行するだけではいけない。自分は実行性能を計測したいのだ。例えば、データベースだと次のような項目を測定したい。

  • 実行に何秒かかったのか
  • CPU使用率の全体の平均や、時間による推移
    • 更にコアごとの使用率の違い
  • I/OのIOPSやスループットの平均、時間による推移

これを見ようと思うと、例えば sar -o perf.dat でデータを取得して、sar -d -f perf.dat で毎秒のI/O性能、sar -P ALL -f perf.dat で各CPUコアの毎秒の使用率を表示させることができるので、その値を加工すればできる。 が、出力がユーザに読みやすいように整形されている分、シェルスクリプトでパパっと集計するには都合がよくない。 さらに、sarを始めとしてsysstat系のiostatmpstatといったプログラムは大抵1秒間隔でしかデータを取れない。サービスで運用しているサーバはそれでよいだろうが、こちとら超高速なシステムの研究をしていたりするので、それでは話にならない。例えば、最初の0.1秒で一気に並列なタスクが立ち上がるのか、0.5秒かかって立ち上がっているのかでは圧倒的に話が違ってくるのである。

というわけで、これらの測定を極めて細かい間隔で測定できるようにperfmongerというツールをこしらえた。 1つのコマンドを実行して、その実行時間や諸々の性能情報を記録するには次のコマンドを叩けば良い。

perfmonger stat --logfile perf.dat --interval 0.1 -- ./test_program

この例では、0.1秒間隔で計測結果を perf.dat に保存している。結果のサマリを見たい場合には、次のコマンドを叩けば良い。

perfmonger summary perf.dat

そうすると、実行時間や、CPU使用率の平均値、各ブロックデバイス毎のI/O性能の平均値などを表示してくれる。

perfmonger summary --json perf.dat

とすると、JSON形式でサマリを出力してくれるので、サマリの値をあとから集計する用途にも良い。

さて、話を実験の自動化へ戻そう。 perfmongerのおかげで計測の作業は極めて単純化されたので、実験を流すスクリプトは次のように書くことができるようになった。

PARAM_LIST=(a b c d)
for param in ${PARAM_LIST[*]}; do
  perfmonger stat --interval 0.1 --logfile perf.$param.dat -- ./test_program $param
done

これで実行すれば、パラメタの値を a、b、c、d と変化させてプログラムを実行し、それぞれの結果が perf.a.dat、perf.b.dat、perf.c.dat、perf.d.dat に保存される。 あとはこれを perfmonger summary などを使って集計していけばよい。していけばよい、といってもそれはそれで面倒な問題があったりするが、ここでは一旦おいておく。

ところで、研究とは「未だ知らぬこと」を明らかにしていく作業である。未だ知らぬからこそ、観測するべきパラメタが a b c d の4つで十分かどうかも分からない。むしろパラメタを何度も変えながら、観測するべき空間に当たりをつけ、さらに注意深く調べるべき部分では細かくパラメタを変化させながらより詳細に分析を進めていくことになる。

つまり、上記のプログラムは、PARAMS_LIST を変えながら何度も実行することになる。

さらに、スクリプトを走らせながら色々考えていると、パラメタa、b、c まで測定が終わったところで、パラメタ c を与えたケースにのみプログラムにバグがあることが見つかったりする。 そうすると、再度実験を走らせ直してやる必要がある。一旦実行をとめて、デバッグし、PARAMS_LIST=(c d) に書き換えて実行する。 これで perf.a.dat、perf.b.dat、perf.c.dat、perf.d.dat が全部出揃ったので、結果を分析してみるとどうもおかしい。やや、プログラムにまたバグがあった。測り直し。

みたいなことをやっていると、perf.*.dat のどのデータは生きていて、死んでいるのかわからなくなってくる。 また、PARAM_LIST を直接何度も変更していると、結局測定に使うべきパラメタのセットはなんだったのかもわからなくなる。

時々刻々と進化していく実験プログラムに対して、それを実行した際の測定結果データが存在するのか、古いプログラムの測定結果データが存在するのか、そして測定に使うパラメタの集合はなにか、これらを管理できる枠組みで実験プログラムの実行を行うことが望ましそうに思えてくる。 あるいは、割り切りとして、毎回フルセットでPARAM_LISTを指定して、実行前に全データを消してしまうという大胆な方法もありうる。

ここで、設計の選択を迫られるのである。

前者のアプローチは、トータルで見たプログラムの実行回数は明らかに最小限にできるであろう。具体的にはMakefileなどを使った実現方法が考えられる。が、シェルスクリプトよりかは一段話がややこしくなり、自動化の部分を組むためにextraな労力が必要になる。 一方、後者は簡単なシェルスクリプトをそのまま使えるので自動化自体にはそれほど手間はかからないはずだ。が、結果を取得しているはずのプログラム実行に関しても何度も繰り返すことになるため、長時間を要するプログラムの実行には不向きに思われる。

この判断は、実際にどんなプログラムをどんな条件で動かすかによって損得が変わってくるので、毎回それを考えるのがダルい。ダルいが、自分の時間は割と貴重な資源なので、実験の全体に要する時間を最小化するために損得の評価を考えてやらねばならない。そこまで含めて自動化できると理想的なのだが。。。

この後にも更に厄介な悩みの種が色々と続くのだが、それはまた後日気が向いたら書くことにする。