PR

progressionで性能を測定・比較

 では,プログラムを実行してみましょう。criterionと同様に,progressionでも性能測定を開始するための準備は自動的には行われません。正確な測定を行うには,「余分なプログラムやプロセスを終了させる」「保留中のプロセスを実行する」といった準備を性能測定の前に行う必要があります。

 progressionを使うプログラムを起動すると,測定結果をどのような名前で記録するかをまず尋ねられます。

 $ ./bench
Store as[]:

 もし何も入力せずにEnterキーを押した場合,プロンプトに測定結果は表示されるものの,この結果は記録として保存されません。

 名前を入力してEnterキーを押すと,以前測定したどの記録と比較するかを聞いてきます。

Store as[]: sort
Compare to []:

 最初の実行時には比較できる記録はないので,何も入力せずにEnterキーを押します。

Compare to []:

 性能測定が実行され,結果が「bench-<入力した記録名>.csv」というCSVファイルに出力されます。同時に,plot.csvというグラフ作成用のCSVファイルが作成されます。plot.csvからgnuplotによりグラフが自動的に作成され,plot.pngという画像ファイルとして保存されます(参考リンク1参考リンク2参考リンク3)。

Executed gnuplot commands:
  set terminal png size 1024,768;
  set output 'plot.png';
  set xtics rotate;
  set xrange [-0.125:2.125];
  set bmargin 8;

  set datafile separator ',';
  set style data boxerrorbars;set style fill pattern;
  plot 'plot.csv' using ($0+0.0):2:3:4:(0.125):xtic(1) title 'naive'

 $ cat bench-sort.csv
Name,Mean,MeanLB,MeanUB,Stddev,StddevLB,StddevUB
"sort/ascending",7.499707031250001e-3,6.093457031249998e-3,9.062207031250002e-3,7.845573930760593e-3,7.750702558575912e-3,7.851857931712595e-3
"sort/descending",6.484228515625e-3,5.781103515625001e-3,6.9529785156250004e-3,2.949415375536227e-3,2.3555573795137775e-3,3.3999542178847235e-3
"sort/randoms",1.218720703125e-2,1.062470703125e-2,1.3280957031249999e-2,6.50520624833167e-3,5.607348145041202e-3,7.262862479870807e-3

 $ cat plot.csv 
sort/ascending,1.0,0.8124926754951362,1.2083414716720706
sort/descending,1.0,0.8915638154476383,1.0722907897015745
sort/randoms,1.0,0.8717917898667437,1.0897457470932794

 グラフ作成時にgnuplotが見つからなかった場合には,CSVファイルだけが作成され,以下のようなエラー・メッセージが出力されます。

Error executing gnuplot; have you got gnuplot installed on your system and in your path?

 $ cat plot.csv 
sort/ascending,1.0,0.8124926754951362,1.2083414716720706
~ 略 ~

 測定プログラムの実行時にすでに他の記録が存在する場合には,「[]」内に表示されます。これらを指定することで比較が行えます。

Compare to [sort, newsort]: sort, newsort

 複数の記録を指定する際の区切り文字には「,」を使います。空白はファイル名の一部とみなされるので注意してください。

Compare to [sort, newsort]: sort newsort
bench: bench-sort newsort.csv: openFile: does not exist (No such file or directry)

 比較対象の記録を指定した場合には,測定結果がcsv,測定結果と以前の記録を比較したグラフがplot.pngに出力されます。

 なお,--mode=オプション(または省略形の-mオプション)にgraphを渡すことで,性能測定を行わずに以前の記録の比較をグラフとして出力させることができます(参考リンク)。

$ ./bench -mgraph
Compare to [sort, newsort]: sort, newsort

 --name=オプション(または省略系の-nオプション)を使うことで,保存するファイルの名前を指定できます。

$ ./bench -n quicksort
Compare to [sort, newsort]: sort, newsort

 比較対象の指定は,--compare=オプション(または省略系の-cオプション)でも行えます。

$ ./bench -n quicksort -c sort, newsort

 --plot-size=オプションを使うことで,criterionと同様に<横幅>x<縦幅>で出力するファイルの大きさを指定できます。以下は,quicksort,sort,newsortの三つの結果を比較するグラフを800x600の画像で出力する指定です。

$ ./bench -mgraph -c quicksort,sort,newsort --plot-size=800x600

 では,出力されたグラフを見てみましょう。新しく測定した結果と比較対象として指定した記録が,左から順に並んでいます(測定した結果がない場合には,比較対象の記録だけが並びます)。棒グラフの箱は平均実行時間を表します。箱の上部から内側にかけて伸びているひげ(whisker)は,実行時間の最小値から最大値までのばらつきの範囲です。

 平均実行時間とばらつきの範囲の両方を示しているのは,性能比較がどの程度妥当なのかを検証するためです。ばらつきの範囲が重なっていなければ,平均実行時間が短いほうが優れているといえます。sortとnewsortを比べたグラフでは,ばらつきの範囲が全く重なっていないので,newsortのほうが速いと結論付けられます。

 一方,ばらつきの範囲が重なっている場合には,より慎重に検討する必要があります。例えばquicksort,newsort,sortの三つの結果を比べたグラフでは,sort/randomsのばらつき範囲に重なりが見られます。「quicksortとnewsortのばらつき範囲の重なりが比較的小さいこと」「sort/randoms以外ではnewsortやsortが圧倒的に速いこと」「sortとnewsortとの比較ではnewsortのほうが速いという結果が出ていたこと」などを考慮すると,この中ではnewsortが最も優秀であると結論付けていいでしょう。

 ただし,quicksortとsortの優劣は慎重に判断する必要があります。sort/randoms以外ではsortのほうが速いものの,sort/randoms関数の平均実行時間はquicksortのほうがsortよりも短く,実行時間のばらつき範囲が重なっています。渡されるリストがランダムな要素からなることがわかっていて,しかも最悪時の最大実行時間が重要な場合には,quicksortを選ぶべきかもしれません。

 なお,ここまで見てきたグラフは,一番左の測定結果の平均実行時間で正規化されています。正規化された結果ではなく,実際の実行時間でグラフを表示したい場合には,--group=オプション(または省略系の-gオプション)でbenchを指定する必要があります。

$ ./bench -mgraph -c sort,newsort -gbench

 もっとも,quicksort,newsort,sortの比較で-gbenchを指定すると,結果がわかりにくくなってしまいます。

$ ./bench -mgraph -c quicksort,sort,newsort -gbench

 このような結果になった場合には,対数グラフで表示させるとよいでしょう。--plot-log-yオプションを指定することで,対数グラフを作成できます(参考リンク)。

$ ./bench -mgraph -c quicksort,sort,newsort -gbench

 ここまでは,オプションを使ってprogressionを使う性能測定プログラムの挙動を変えてきました。progressionも,criterionと同様にオプションを使わずに挙動を変更できます。Progression.MainモジュールのdefaultMainWith関数に,Progression.Configモジュールで提供されているConfig型の値を渡す方法です。Progression.ConfigではdefaultConfigは提供されていませんが,Config型およびConfig型フィールドに格納される値の型は,それぞれMonoidクラスのインスタンスになっています。したがって,mempty関数で作成した値のフィールドを書き換えることで,プログラムのデフォルトの挙動を変えられます。

import Data.Monoid
import Progression.Config
import Progression.Main
import Criterion

main = defaultMainWith (mempty {cfgGraph=mempty {graphSize=Just (640, 480)}}) $
~ 略 ~

 特定のオプションを毎回指定するようなら,mempty関数で作成したConfig型の値のフィールドを書き換えて,デフォルトの挙動を変えたほうがよいでしょう。