# StateT vs. IORef: a benchmark

Published on ; updated on

Sometimes I’m writing an IO loop in Haskell, and I need some sort of a counter or accumulator. The two main options are to use a mutable reference (IORef) or to put a StateT transformer on top the IO monad.

I was curious, though, if there was a difference in efficiency between these two approaches. Intuitively, IORefs are dedicated heap objects, while a StateT transformer’s state becomes “just” a local variable, so StateT might optimize better. But how much of a difference does it make?

So I benchmarked the four functions, all of which calculate the sum
of numbers between 1 and `n = 1000`

.

`base_sum`

simply calls `sum`

from the base
package; `state_sum`

and `stateT_sum`

maintain the
accumulator using the `State Int`

and
`StateT Int IO`

monads, respectively, and
`ioref_sum`

uses an `IORef`

within the
`IO`

monad. And here are the results, as reported by
criterion.

I’m not sure how `stateT_sum`

manages to be faster than
`state_sum`

and `base_sum`

(this doesn’t appear to
be a statistical fluke), but what’s clear is that `ioref_sum`

is significantly slower of them all.

So if 3ns per state access matter to you, go for `StateT`

even when you are in `IO`

.

(Update: also check out the comments on reddit, especially the ones by u/VincentPepper.)

Here’s the full benchmark code. It was compiled with `-O2`

by GHC 8.8.4 and run on AMD Ryzen 7 3700X.

```
import Criterion
import Criterion.Main
import Control.Monad.State
import Data.IORef
base_sum :: Int -> Int
= sum [1 .. n]
base_sum n
state_sum :: Int -> Int
= flip execState 0 $
state_sum n 1..n] $ \i ->
forM_ [+i)
modify' (
stateT_sum :: Int -> IO Int
= flip execStateT 0 $
stateT_sum n 1..n] $ \i ->
forM_ [+i)
modify' (
ioref_sum :: Int -> IO Int
= do
ioref_sum n <- newIORef 0
ref 1..n] $ \i ->
forM_ [+i)
modifyIORef' ref (
readIORef ref
= do
main let n = 1000
defaultMain"base_sum" $ whnf base_sum n
[ bench "state_sum" $ whnf state_sum n
, bench "stateT_sum" $ whnfAppIO stateT_sum n
, bench "ioref_sum" $ whnfAppIO ioref_sum n
, bench ]
```