Skip to content

FastStringTable reallocates a `FastMutInt` for each `FastZString`

Performance analysis reported that we allocate 100_000 FastMutInts on the agda code base in a ghc session:

key; total; count; max; avg
ghc-9.9-inplace:GHC.Data.FastMutInt:FastMutInt[ARR_WORDS]; 1847472; 115467; 16; 16.0

In FastStringTable, we count the number of z-encoded FastStrings that exist in a GHC session. We UNPACK the counters to not waste memory, but live retainer analysis showed that we allocate a lot of FastMutInts, retained by mkFastZString.

We lazily compute the FastZString, only incrementing the counter when the FastZString is forced. The function mkFastStringWith calls mkZFastString and boxes the FastMutInt, leading to the following core:

mkFastStringWith
  = \ mk_fs _  ->
         = case stringTable of
            { FastStringTable _ n_zencs segments# _ ->
                ...
                     case ((mk_fs (I# ...) (FastMutInt n_zencs)) -- <-- this reallocates
                        `cast` <Co:2> :: ...)
                        ...

Marking this field as NOUNPACK avoids this reboxing, eliminating the allocation of a fresh FastMutInt on every FastString allocation.

Edited by Hannes Siebenhandl
To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information