Search code examples
haskellioref

Memory footprint and performance of IORef


I wonder what is the memory footprint of a variable from type IORef a if I know that the size of a is x. Also what is the expected performance of the function writeIORef applied to integer compare to say a regular variable assignment (like x = 3) in, say, Java ?


Solution

  • In Haskell, an IORef a behaves like a single-element mutable array. The definition of IORef is the following, disregarding newtype wrapping:

    data IORef a = IORef (MutVar# RealWorld a) 
    

    Here, MutVar# RealWorld a is a primitive mutable reference type. It is a pointer which points to two words, a header, and a payload which is itself a pointer to a normal lifted Haskell object. Hence the overhead of MutVar is two words (16 byte on 64 bit systems) and one indirection.

    The overhead of MutVar# is thus one extra indirection and one extra header word. This is unavoidable. In contrast, the overhead of the IORef constructor is also one header word and one indirection, but it can be eliminated by unpacking IORef:

    data Foo a = Foo !(IORef a) a a 
    

    Here, the bang on IORef causes the underlying MutVar to be unpacked into Foo. But while this unpacking works whenever we define new data types, it does not work if we use any existing parameterized type, like lists. In [IORef a], we pay the full cost with two extra indirections.

    IORef will be also generally unpacked by GHC optimization if it is used as an argument to a function: IORef a -> b will be generally unboxed to MutVar# RealWorld a -> b, if you compile with optimization.

    However, all of the above overheads are less important than the overhead in garbage collection when you use a large number of IORef-s. To avoid that, it is advisable to use a single mutable array instead of many IORef-s.