In GHCI, I run this simple test:
encodeFile "test" [0..10000000]
The line runs really quickly (<10sec), but my memory usage shoots up to ~500MB before it finishes. Shouldn’t encodeFile be lazy since it uses ByteString.Lazy?
Edit: Roman’s answer below is great! I also want to point out this answer to another question, that explains why Data.Binary does strict encoding on lists and provides a slightly more elegant work around.
Here’s how serialization of lists is defined:
That is, first serialize the length of the list, then serialize the list itself.
In order to find out the length of the list, we need to evaluate the whole list.
But we cannot garbage-collect it, because its elements are needed for the second
part,
mapM_ put l. So the whole list has to be stored in memory after thelength is evaluated and before the elements serialization starts.
Here’s how the heap profile looks like:
Notice how it grows while the list is being built to compute its length, and
then decreases while the elements are serialized and can be collected by the GC.
So, how to fix this? In your example, you already know the length. So you
can write a function which takes the known length, as opposed to computing it:
This program runs within 53k of heap space.
You can also include a safety feature into
putWithLength: compute the length while serializing the list, and check with the first argument in the end. If there’s a mismatch, throw an error.Exercise: why do you still need to pass in the length to
putWithLengthinstead of using the computed value as described above?