分かる人は分かる某ISSと呼称されるプログラムをメンテナンス バージョンアップすることになりました。その時、気になったのは以下の2点です。
- 試行錯誤で組み上げたので、ソースコードに無駄や回り道が多い (これは、ソースコードのメンテナンス性を損なう)
- ジャーナリングしたユーザー操作をプレイバックする自動テストが2分も掛かって重い (頻繁に実行できないと、間違いの混入が即座に検出できない。即座に検出できないと、どこを書き換えたことが問題の発生原因か分かりにくい)
というわけで、主に自動テストの高速化をテーマにソースコードの体質改善を行ってみました。基本的に外部仕様は一切変更せず、見た目や読み書きするファイルも完全に同等です。
基本的にはNProfを使ってプロファイリングしつつ、ボトルネックと思われる部分を改善していく作業となります。
結果 §
以下の結果は、いずれもVisual Studio 2008を用いたDebugビルドによります。ISSのリリース版はVisual Studio 2005でビルドしているので、その点で旧版とは完全に一致しません。しかし、速度比較という意味で同じ前提に合わせてあります。
端数は四捨五入しています。ISS2はあまりに桁数が小さいので、1/10秒の桁まで入れています。(四捨五入の結果、その桁はいずれも0になってしまいましたが)
自動テストを2回行っているのは、JITが動作するタイミングの関係上、初回実行と2回目の実行では差が出る可能性があるためです。2回目がJIT抜きの純粋な実行時間と思われます。
ISS1(チューン前) §
1回目2:03
2回目2:02
ISS2(チューン後) §
1回目0:04.0
2回目0:03.0
2回目の値を使った速度比は((2*60)+2)/3=約41となります。
つまり、おおむね40倍の速度アップです。
2008/04/11追記 §
高速化ではなくソースコードの扱いやすさに主目的を置いた若干のコード改良を行ったところ、更に速くなりました。
1回目0:03.6
2回目0:02.5
ただし、2回目は時間が短く誤差の範囲が大きいので、5回程度実行した後で確からしい値のほぼ中間値を選んで記述しています。
速度比は((60*2)+2)/2.5=48.8となり、約40倍から約48倍に拡大しました。
このあたりがはっきり目に見える改良の打ち止めかと思ったのに、まだ速くなったのでびっくり。
感想 §
いや~。10倍ぐらいは行くだろうと思っていましたが。
まさか40倍とは。
ちなみに、印象に残るポイントを以下に書きます。
- アルゴリズムの改善効果は大きい
- リフレクションは予想以上に重かった (カスタム属性の取得等)
- 逆にDictinalyを、「通常のクラスメンバ+リフレクションによる列挙」に置き換えて高速化できたケースも
- stringのStartsWithが予想以上に重かった
- 終盤では、地道なチューニングの効果が大きかった (たとえば条件判定を行うときに重い判定よりも先に軽い判定を行うであるとか、値のチェックを下位レイヤーでやっていれば上位レイヤーでは同じ判定をしないであるとか)
- 常識ではあるが、やはりプロファイラがもたらす数値は漠然とした思い込みよりも的確 (変更の必要性を意識していたにも関わらず、変更しなかったコードも多数)
それはともかく、直せば直すほど速くなっていくのはかなり快感だと思いました。
こういう快感は、Win/VのWidowを書いたとき以来かも。これはMS純正wifeman.dll+DDD相当でしたが、桁違いに高速チューンしました。残念ながらWin/Vに含まれたTrueTypeフォントドライバが遅くて、速くなったことがほとんど分かって貰えなかったのですが。
補足 §
このケースにおいて、高速性はテストの反復実行回数を増やせることを意味します。(まさに、そのような観点に絞ってチューニングしている。通常実行時のレスポンスなどは一切配慮していない)
それは、誤ったコードの混入の早期検出と、素早い除去のために役立ちます。検出が早い方が、変更点が少ないので誤りを見つけやすいのです。
それゆえに、高速性は品質と生産性向上に結びつきます。
だからこそ、チューンナップはストレートに快感です。