mtl(実装はtransformers)で提供されているモナド変換子のLazy版とStrict版で、どのくらいパフォーマンスの差が出るか調べてみた。また、私がCPS(継続渡しスタイル)で実装したものとも比較してみた。
比較方法
{-# LANGUAGE FlexibleContexts #-} import Control.Monad.Reader.Class import Control.Monad.Reader as R import Control.Monad.Reader.CPS as RC import Control.Monad.Writer.Lazy as WL import Control.Monad.Writer.Strict as WS import Control.Monad.Writer.CPS as WC import Data.Monoid import Control.Monad.State.Class import Control.Monad.State.Lazy as SL import Control.Monad.State.Strict as SS import Control.Monad.State.CPS as SC askTest :: MonadReader Int m => (m () -> Int -> t) -> t askTest f = f (replicateM_ 10000000 ask) 42 tellTest :: MonadWriter (Sum Int) m => (m () -> t) -> t tellTest f = f (forM_ [1..1000000] $ tell . Sum) getTest :: MonadState Int m => (m () -> Int -> t) -> t getTest f = f (replicateM_ 10000000 get) 42 putTest :: MonadState Int m => (m () -> Int -> t) -> t putTest f = f (forM_ [1..10000000] put) 42 modifyTest :: MonadState Int m => (m () -> Int -> t) -> t modifyTest f = f (replicateM_ 1000000 (modify succ)) 0
のそれぞれの関数で3回時間を測り、もっとも短かったものを結果とする。
結果
Target | Lazy | Strict | CPS |
---|---|---|---|
askTest runReader | 0.90s | 0.84s | |
tellTest runWriter | 1.56s | 1.34s | 1.14s |
getTest runState | 5.58s | 0.70s | 0.41s |
putTest runState | 5.54s | 1.14s | 0.81s |
modifyTest runState | 0.61s | 0.39s | 0.08s |
やはり、StrictのほうがLazyよりも数段速い。だが、CPS版はさらに速い。継続の力、恐るべし…
まとめ
モナドはCPSで実装しよう。