テストするコードをテストしてみようと思い立ち、Google Test https://code.google.com/p/googletest/ を利用した記録。

数値シミュレーションのコード、特にN体シミュレーションのコード開発は、 自動テストをしながら開発するのには本質的にあまりむいていない。 これは、全体テストにかかる時間のほうがシミュレーションの計算より時間がかかりかねないし、 MPIで並列化されているコードで大規模なテストはバッチジョブとでしか実行できない場合、 自動テスト化するにはそれ自体が別の一仕事になってしまう。 個人でおこなうには多少無理がある。

それでも、個々の関数や小さなクラスにユニットテストをおこなう価値はあるかもしれない。 必要があって試してみようかと思いつく例として、 既存のコア関数を最適化してSSE/AVX化するという場合がある。 AVX化して4倍高速にするぜといきっていても、ベクトル長に関わるループの間違いが混入したりするのはありがちだし、 intrinsicを使うことで計算自体を間違う可能性は大きい。 これは既存のコードをCUDA/OpenCL化するときも同様で、 参照実装とOpenCLコードの結果を比較せずにコードを書くことはできない。 このような場合、経験的にはその度に自分で小さなテスト用プログラムを書いて、 適当なセットの入力値に対して同じ結果になるか、 あるいは誤差がulpだとか期待される相対誤差範囲にあるかを調べて「できた!」と思って終わりにすることはよくある。 ほとんどの場合にはそれで問題なく、広い意味でのSIMD化は成功といえるのだが。しかし。

以下のURLに、International HPC Summer School 2014の講義スライドがアップロードされている。 その中に”HPC Software Engineering”と題した講義があった。

Internation HPC Summer School 2014 Schedule https://www.xsede.org/web/international-hpc-summer-school/2014-wiki/-/wikid/9khB/2014+Wiki/Full+Schedule

スライド:lindahl_hpc_software_engineering_20140605.pdf

ローカルコピー:http://galaxy.u-aizu.ac.jp/permanent/lindahl_hpc_software_engineering_20140605.pdf

ここで紹介されているGROMACSというオープンソースで開発されている分子動力学コードの開発では、 様々なsoftware engineeringの手法が利用されている。 具体的に利用しているソフトウエアは、Git(ソースコード管理), CMake(build system), Doxygen(C++クラス文書生成), Google Test(テスト), gerrit(コードレビュー), Jenkins(継続的インテグレーション), Redmine(バグ・プロジェクト管理), copernicus(高レベルでのコード実行の制御)になる。 最後のものはsoftware engineeringとは直接関係ない。詳しくはスライドを参照して欲しい。 これらの手法を一度に全て採用するわけにも行かないが、それぞれは以前より見聞きしたことはあったので、 GoogleTestとCMakeを使って、最近必要になったコード書き換えをおこなった。

このコード書き換え(リファクタリング)は、ある関数を動作を変えずに速度について最適化したい、という目的がある。 入力のパターンは膨大なため、全てをテストすることは不可能であるから、 これまで似たようなリファクタリングをやった時には、乱数をベースに入力を発生させて、 元の関数の出力と比較するということを100万パターン行うプログラムを作った。 そして、そのプログラム自体を、shell script等で複数回実行するということをやってきた。 よくある「車輪の再発明」で、これまでこういうことを違う問題に対して何度も行ったことがある。 そのたびに乱数で入力を発生させて処理を繰り返すコードを、単純なことでもあるので、 昔のソースを探すよりは手っ取り早くその場でアドホックに作っていた。

このような時にGoogleTestを使うとどうなるか。結果からいうと、このような無限に近いパターンを検証したいなら、 非常に簡単だし役に立つ。そのためにはGoogleTestの利用方法を学ぶ必要があるが、それは 「Google Test ドキュメント日本語訳」 http://opencv.jp/googletestdocs/index.html を参照した。 さらに、Google Test自体をより簡単に利用するために、githubで公開されている https://github.com/jpilet/gtestcmake CMakeスクリプトを利用した。ただし、このオリジナルは今回利用した環境では、pathの問題があったので、 修正を加えた。一行ではあるが修正したものは https://github.com/dadeba/gtestcmake にforkした。 このCMakeスクリプトを使うと、最初にconfigurationした時に Google Testの最新コードを自動的にcheckoutしてくれるので非常に便利であった。

1
2
3
4
5
6
7
cd project_directory_somewhere
cp -r /elsewhere/gtestcmake/ .
vim CMakeLists.txt
mkdir build
cd build
cmake ../
make

CMakeLists.txtにGoogle Testに対応したテスト用のコードの依存関係を書く。 cmakeコマンドを実行するとMakefileがbuildディレクトリに生成されるので、 あとはそれを使ってテスト用の実行ファイルが作ることができる。 上記ドキュメント日本語訳には、様々なテスト記述方法や オプションが説明されている。 その中で特に今回の目的に役立ったのは「テストを繰り返す」 http://opencv.jp/googletestdocs/advancedguide.html#adv-repeating-the-tests の部分。 Google Testのライブラリとリンクすると、ここで説明されている実行時引数を指定することができる。 今回のように検証すべき入力が乱数で生成可能で、しかも無限のようにある場合、

1
./my_test_code --gtest_repeat=1000 --gtest_break_on_failure

とするだけで、このテストを1000回繰り返し、 途中1つでも失敗したらそこで実行が止まる。別途スクリプトを書いたりする必要がない! これは予期していなかったよい点であった。 この方法を使って一応十分に検証したコードの速度は、リファクタリング前より3倍高速になった。めでたし。