You can create a mutable Int using MutableByteArray#.
import GHC.Exts
import GHC.Types
data Ref = Ref# {-# UNPACK #-} !(MutableByteArray# RealWorld)
new :: Int -> IO Ref
new (I# i) = IO (\s -> case newByteArray# 8# s of
(# s, a #) -> case writeIntArray# a 0# i s of
s -> (# s, Ref# a #))
read :: Ref -> IO Int
read (Ref# a) = IO (\s -> case readIntArray# a 0# s of
(# s, i #) -> (# s, I# i #))
write :: Ref -> Int -> IO ()
write (Ref# a) (I# i) = IO (\s -> case writeIntArray# a 0# i s of
s -> (# s, () #))
But I think this representation is wastes a few bytes. Assuming MutableByteArray# works like ByteArray#, the memory layout is:
But the size is always the same, so there is no need to have a size field. Looking at the GHC wiki, it seems like the size of a heap object and other useful information for the garbage collector is stored elsewhere in a info table that is pointed by the header. I assume the info table will tell the GC to look at the size field. Iâd like for the info table to just say âthe size of this object is Xâ.
I looked at some unboxed mutable reference implementations (e.g. unboxed-ref and primdata) and they are using MutableByteArray# so, if my reasoning is right, they also waste a few bytes of memory.
I have three questions:
How do I implement a mutable Int that doesnât waste any memory?
Is my reasoning about the memory layout correct? More generally, how do I answer questions about the runtime memory layout this?
What is the canonical way of finding the size of Int in bytes? In the example above I just assumed itâs 8. I found the answer: enable the C preprocessor, include MachDeps.h and use SIZEOF_HSINT.
I understand the sentiment, but is this really important? If you need bulk mutable ints, then the size field overhead dwindles behind the mass of data itself.
Itâs completely undocumented but still possible: you can DIY your own heap objects in Cmm. You also need to write a few other Cmm functions to allocate your heap object in the current nursery, and to read/write its payloads, then use foreign import prim to import those functions into Haskell.
You may want to grep the INFO_TABLE macro and ALLOC_PRIM macros in the rts Cmm files for some existing examples.
Youâre right, I omitted the âif you know what youâre doing suffixâ. I meant âsafeâ in the sense that the written Cmm code indeed does what itâs supposed to do, but perhaps that notion of safety isnât very meaningful in the first place.
So the âDIY heap objectâ approach takes slightly less memory and is (therefore?) slightly faster. These benchmark results should apply, so it is around 4% faster on average.
Or maybe there is a difference between the atomic and non-atomic operations?
Wow, you beat fetchAddIntArray#? Does it mean fetchAddIntArray# can be removed from GHC or is your approach applicable only to some use cases (pardon the ignorant questions, Iâm just a hapless (and grateful) user)?
fetchAddIntArray# as itâs name implies works over arrays while this custom C-- implementation works only on a single value. So it is not a full replacement.
Also note that I havenât done any work on this; @TerrorJack and @Bodigrim did.