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