Space leak
Memory a program holds longer than it needs to. In a lazy language the usual cause is a chain of unevaluated thunks: a lazy left fold over a list builds one suspended addition per element and only performs them at the end. A heap profile by closure type shows it as a growing THUNK band.
A space leak is not a leak in the C sense: the memory is reachable and will be freed eventually. The program is still wrong about how much it keeps, and the difference can be the whole input.
Haskell. The lazy foldl builds a thunk per element before adding any of them; foldl' forces the accumulator at each step.
import Data.List (foldl')leaky = foldl (+) 0 [1 .. 10000000 :: Int]steady = foldl' (+) 0 [1 .. 10000000 :: Int]
The other common cause is retention: sum xs / fromIntegral (length xs) traverses xs twice, so the whole list has to be kept between the two traversals even if it was produced lazily. Computing both in one fold lets each cell be collected as soon as it is read.
Heap profiling finds both. GHC's +RTS -hT breaks the live heap down by closure type, and a leak of suspended work shows up as a THUNK band that grows over the run instead of staying flat.
see also
- Tail callA tail call is a call made as the last action of a function, whose result the caller returns unchanged. Since the caller has nothing left to do, its stack frame can be reused, and the call compiled as a jump; a language that does this for every tail call is properly tail recursive, and loops can be written as recursion without the stack growing. OCaml, Scheme and most functional languages guarantee it; the JVM, Python and most JavaScript engines do not.
- Generational GCA garbage collector that splits the heap by age, because most objects die young: new objects go to a small nursery collected often by copying, and survivors are promoted to a larger heap collected rarely. A write barrier records old-to-young pointers so that a minor collection need not scan the old heap.
- Cheney's algorithmCheney's algorithm is a copying garbage collector that moves every live object from one half of the heap, from-space, to the other, to-space, in breadth-first order, using the copied objects themselves as the queue: a scan pointer follows the free pointer through to-space, copying whatever the scanned objects point to. It needs no stack or extra memory, leaves a forwarding address in each moved object so shared and cyclic structures are copied once, compacts the live data, and costs time proportional to the live data, not the heap. It is the basis of the young-generation collectors of most language runtimes.
- Lazy evaluationLazy evaluation, or call-by-need, delays computing an expression until its value is needed and then remembers the value, so it is computed at most once. It lets programs define infinite structures and consume only part of them, separates generating candidate values from choosing among them, and avoids work whose result is never used. Its costs are the memory held by unevaluated suspensions, which can cause space leaks, and harder reasoning about when work happens. Haskell is lazy by default; OCaml provides it explicitly through Lazy.t and Seq.
- Weak referenceA weak reference is a pointer the garbage collector ignores when deciding reachability, so a cache or registry can refer to a value without keeping it alive; once nothing else refers to the value, it may be collected and the weak reference then reads as empty. Weak references alone do not make a memo table safe, because a cached value that mentions its key keeps the key alive through the table. An ephemeron fixes this: it holds a key weakly and a value that is retained only while the key is reachable from elsewhere.
referenced by
further reading
- [1]P. Wadler, “Fixing some space leaks with a garbage collector”, Software: Practice and Experience 17 (1987).
- [2]C. Runciman, D. Wakeling, “Heap profiling of lazy functional programs”, Journal of Functional Programming 3 (1993).
last updated