PR

任意の式に対するプロファイル

 次に,main変数のどの部分で時間がかかっているかを調べてみましょう。GHC拡張として用意されているSCC指示文を使うことで,プログラムの中の特定の式を「コスト集約点(cost centre,コストセンタ)」に指定できます。SCCは「Set Cost Centre」を意味します。コスト集約点として指定することで,式を処理するのに必要な実行時間やメモリー使用量といった情報を取得できるようになります。

import Control.Monad (forM_)
import Prelude hiding (lookup)
import Data.HashTable (new, newHint, hashInt, insert, lookup)

main = do
    m <- {-# SCC "new" #-} new (==) hashInt
    {-# SCC "insert" #-} forM_ [1..10000000] $ \n -> insert m n n
    v <- {-# SCC "lookup" #-} lookup m 100
    print v

 SCC指示文を式の左側に置くことで,右側の式をコスト集約点に指定できます。この例では,「new (==) hashInt」をnewという名前のコスト集約点,「forM_ [1..10000000] $ \n -> insert m n n」をlookupという名前のコスト集約点にしています。

 SCC指示文は,変数や関数を定義する式に対しても使えます。例えば,main変数をコスト集約点にできます。

main = {-# SCC "main" #-} do
~ 略 ~

 ただし,SCC指示文を変数や関数に直接付けることはできません。そうした場合には,パース・エラーになります。

変数束縛の例:

{-# SCC "main" #-} main = do
~ 略 ~

$ ghc -O2 -prof -auto-all HashTable.hs -rtsopts
[1 of 1] Compiling Main             ( HashTable.hs, HashTable.o )

HashTable.hs:7:1: Parse error in pattern: _scc_ "main" main

do式での変数束縛の例:

main = do
    {-# SCC "new" #-} m <- new (==) hashInt

$ ghc -O2 -prof -auto-all HashTable.hs -rtsopts
[1 of 1] Compiling Main             ( HashTable.hs, HashTable.o )

HashTable.hs:8:5: Parse error in pattern: _scc_ "new" m

 関数や変数の定義に対してSCC指示文を付けることで,-auto-allオプションを使った場合と同じ結果を得られます。-auto-allオプションは,トップレベルで定義された変数や関数に対し,自動的にSCC指示文を付与する機能なのです。ただし,-auto-allオプションによるSCC指示文の付与は,INLINE指示文によりインライン化される関数に対しては行われません。インライン化される関数を対象にしたい場合には,SCC指示文を使って明示的にコスト集約点を指定する必要があります。

 それではプログラムをビルド・実行して結果を見てみましょう。

$ ghc -O2 -prof -auto-all HashTable.hs -rtsopts
[1 of 1] Compiling Main             ( HashTable.hs, HashTable.o )
Linking HashTable ...

$ ./HashTable +RTS -p
Just 100

~ 略 ~
COST CENTRE                    MODULE               %time %alloc

insert                         Main                 100.0  100.0


                                                                                               individual    inherited
COST CENTRE              MODULE                                               no.    entries  %time %alloc   %time %alloc

MAIN                     MAIN                                                   1           0   0.0    0.0   100.0  100.0
 main                    Main                                                 226           1   0.0    0.0    73.5   66.2
  lookup                 Main                                                 231           1   0.0    0.0     0.0    0.0
  insert                 Main                                                 228           1  73.5   66.2    73.5   66.2
  new                    Main                                                 227           1   0.0    0.0     0.0    0.0
 CAF                     Main                                                 220           4   0.0    0.0    26.5   33.8
  main                   Main                                                 229           0   0.0    0.0    26.5   33.8
   lookup                Main                                                 232           0   0.0    0.0     0.0    0.0
   insert                Main                                                 230           0  26.5   33.8    26.5   33.8
~ 略 ~

 この結果から,main関数の中でも要素を挿入している部分が特に時間やメモリーを消費していることがわかります。Data.HashTableで提供されているハッシュテーブルは,要素数が閾値を超えるごとに拡張する必要があります。この結果は,大量の要素の挿入とそれに伴うハッシュテーブルの拡大が,プログラムの実行時間や消費メモリーの大半を占めることを示しています。