I did some Criterion benchmarks to estimate how much performance I lose by running my code over a monad stack. The results were rather curious, and I have probably stumbled upon some laziness pitfall in my benchmark.
The benchmark tells me that running WriterT String IO
is 20 times(!) slower than running plain IO
, even when not using tell
. Weirdly, if I stack WriterT
with ReaderT
and ContT
it is just 5 times slower. This probably is a bug in my benchmark. What am I doing wrong here?
The benchmark
{-#LANGUAGE BangPatterns#-}
module Main where
import Criterion.Main
import Control.Monad
import Control.Monad.Writer
import Control.Monad.Reader
import Control.Monad.Cont
process :: Monad m => Int -> m Int
process = foldl (>=>) return (replicate 100000 (\(!x) -> return (x+1)))
test n = process n >> return ()
main = defaultMain [
bench "Plain" t0
,bench "Writer" t1
,bench "Reader" t2
,bench "Cont" t3
,bench "RWC" t4
]
t0 = test 1 :: IO ()
t1 = (runWriterT (test 1:: WriterT String IO ()) >> return ()) :: IO ()
t2 = (runReaderT (test 1:: ReaderT String IO ()) "" >> return ()) :: IO ()
t3 = (runContT (test 1:: ContT () IO ()) (return) >> return ()) :: IO ()
t4 = ((runWriterT . flip runReaderT "" . flip runContT return $
(test 1 :: ContT () (ReaderT String (WriterT String IO)) ())) >> return ()) :: IO ()
The results
benchmarking Plain mean: 1.938814 ms, lb 1.846508 ms, ub 2.052165 ms, ci 0.950 std dev: 519.7248 us, lb 428.4684 us, ub 709.3670 us, ci 0.950 benchmarking Writer mean: 39.50431 ms, lb 38.25233 ms, ub 40.74437 ms, ci 0.950 std dev: 6.378220 ms, lb 5.738682 ms, ub 7.155760 ms, ci 0.950 benchmarking Reader mean: 12.52823 ms, lb 12.03947 ms, ub 13.09994 ms, ci 0.950 std dev: 2.706265 ms, lb 2.324519 ms, ub 3.462641 ms, ci 0.950 benchmarking Cont mean: 8.100272 ms, lb 7.634488 ms, ub 8.633348 ms, ci 0.950 std dev: 2.562829 ms, lb 2.281561 ms, ub 2.878463 ms, ci 0.950 benchmarking RWC mean: 9.871992 ms, lb 9.436721 ms, ub 10.37302 ms, ci 0.950 std dev: 2.387364 ms, lb 2.136819 ms, ub 2.721750 ms, ci 0.950