PR

性能測定・比較のためのプログラムを書く

 progressionを利用するプログラムの例を以下に示します。

import Progression.Main
-- import Criterion.Main hiding (defaultMain)
import Criterion
import System.Random (getStdGen, randoms)

import Data.List (sort)

main = do
     g <- newStdGen
     defaultMain $
       bgroup "sort" [ bench "ascending"   $ nf sort generator
                     , bench "descending"  $ nf sort (reverse (generator))
                     , bench "randoms"     $ nf sort $ take (10^4) $ (randoms g::[Int])
                     ]

generator :: [Int]
generator = [0..10^4]

 progressionでは,Progression.MainモジュールのdefaultMain関数を使って性能を測定します。defaultMain関数では,criterionのBenchmark型を使って性能測定を行います。

Prelude Progression.Main> :t defaultMain
defaultMain :: Criterion.Types.Benchmark -> IO ()

 Progression.MainモジュールのdefaultMain関数には,BenchMark型のリストではなくBenchMark型を渡す点に注意してください。また,defaultMainという関数の名前が,criterionのCriterion.Mainモジュールにある同名の関数と衝突してしまう点にも注意する必要があります。defaultMainやdefaultMainWithの名前の衝突を避けるには,hidingなどを使ってCriterion.Mainモジュールからインポートする対象を取り除くか,Criterionモジュールから必要なcriterionの関数や型などをインポートする必要があります。こうした注意点を除けば,progressionを使うプログラムはcriterionを使うプログラムとほぼ同じ形で記述できます。

 上に示したプログラムは,前回のcriterionを使うプログラムと同様に,「昇順に整列したリスト」「降順に整列したリスト」「ランダムに生成されたリスト」に対し,Data.Listモジュールのsort関数を適用する際の性能を測定するものです。プログラムの性能を比較するには,前に示したプログラムに加え,以下のような比較対象のプログラムを用意する必要があります。

import Progression.Main
-- import Criterion.Main hiding (defaultMain)
import Criterion
import System.Random (getStdGen, randoms)

import NewSort (sort)

main = do
     g <- newStdGen
     defaultMain $
       bgroup "sort" [ bench "ascending"   $ nf sort generator
                     , bench "descending"  $ nf sort (reverse (generator))
                     , bench "randoms"     $ nf sort $ take (10^4) $ (randoms g::[Int])
                     ]

generator :: [Int]
generator = [0..10^4]

 この例では,GHC 6.12.3のData.Listモジュールで提供されているsort関数の代わりに,NewSortモジュールで定義されているGHC 7.0.1の新しいsort関数を使って性能を測定しています。このように,progressionでは性能測定を行うプログラムを複数用意し,それぞれの測定結果から性能を比較します。

 プログラムを工夫すれば,性能測定の対象ごとにプログラムを用意しなくても,一つのプログラムで複数の対象の性能を測定できます。例を見てみましょう。

import Progression.Main
import Criterion
import System.Random (getStdGen, randoms)
import System.Environment (getArgs, withArgs)

import qualified Data.List (sort)
import qualified NewSort (sort)

main = do
     g <- getStdGen

     args <- getArgs
     let sort = case head args of
                  "sort"    -> Data.List.sort
                  "newsort" -> NewSort.sort
                  _         -> error "Unkown sort function!"

     withArgs (tail args) $ do
       defaultMain $
         bgroup "sort" [ bench "ascending"   $ nf sort generator
                       , bench "descending"  $ nf sort (reverse (generator))
                       , bench "randoms"     $ nf sort $ take (10^4) $ (randoms g::[Int])
                       ]

 このプログラムでは,どのモジュールで定義されたsort関数を使うかを,プログラムに渡す第1引数で選択するようにしています。引数の取得には,System.EnvironmentモジュールのgetArgs関数を使っています。次いでSystem.EnvironmentモジュールのwithArgs関数からdefaultMain関数を呼び出すことで,2番目以降の引数をdefaultMain関数に渡しています。

 このようにgetArgsとwithArgsを組み合わせることで,引数を受け取るdefaultMain関数の挙動を変えられます。その結果,複数のプログラムを作らなくても,第1引数として渡す値により,測定する対象を選択できるようになります。

 withArgsにはほかにも使い道があります。例えば,withArgsを使ってdefaultMainに与える引数を加工することもできます。先の例では,プログラムに渡す第1引数の"sort"や"newsort"という文字列はdefaultMainにとって無効な引数であるため,使わずに捨てていました。withArgsを使えば,第1引数をdefaultMainにとって有効な文字列に加工することもできます。

     withArgs (("-n" ++ (head args)) : tail args) $ do
       defaultMain $
         bgroup "sort" [ bench "ascending"   $ nf sort generator
                       , bench "descending"  $ nf sort (reverse (generator))
                       , bench "randoms"     $ nf sort $ take (10^4) $ (randoms g::[Int])
                       ]

 この例では,「-n」という文字列を第1引数の先頭に追加することで,defaultMainにとって有効な文字列に加工しています(「-n」は保存するファイルの名前を指定するオプションです。詳しくは後述します)。そして,加工した第1引数を「2番目以降の引数からなるリスト」の先頭に置くことで,加工された第1引数を含むすべての引数を,withArgsを通じてdefaultMainに渡しています。この結果,第1引数として渡された"sort"や"newsort"は,「どのモジュールで定義されたsort関数を使うかの指定」と「defaultMain関数に渡す"-nsort"や"-nnewsort"といったオプションの指定」の二つの役割を担うことになります。

 このように,getArgsとwithArgsを使えば,progressionを使う性能測定・比較プログラムの動作をカスタマイズできます。