Build1 publisher2 min readPublished
Mark Shannon proposes a two-generation incremental GC after CPython reverted his 3.14 collector
Mark Shannon told the Python Language Summit that GC takes 11.67% of CPython's runtime and proposed a collector that is both generational and incremental. His 3.14 incremental collector was reverted over memory pressure, so the hybrid must match its short pauses on less memory.
The Engineer · Build desk

What happened
- Shannon's 3.14 collector cut peak collection pauses to tens of milliseconds, against about 3 seconds for the generational collector CPython uses now.
- That collector was non-generational, keeping the whole heap in a single generation, a layout that carries a cost in memory use.
- His replacement design has one young and one old generation and alternates collection increments between the two.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- exposure Servers and applications with user interfaces, the workloads Shannon named as most hurt by pauses, stay on the multi-second-pause collector until a hybrid ships.
- decision Without a hybrid, CPython has to pick a default that favours either memory or pause time, because each existing collector wins on only one of the two.
- cost JIT speedups enlarge the collector's share of runtime: halving the interpreter's 30.6% would lift collection from 11.67% to about 13.8% if nothing else changed.
Shannon defined a metric before he pitched a design, and I think that is the right order of work. Scavenge effectiveness is objects collected divided by objects visited [8]. Inverted, the generational collector's 0.3% means it visits about 333 objects for every one it frees, and the reverted collector's 1% means about 100 [1]. Put plainly, the garbage collector spends nearly all its visits on objects that are not garbage [1].
Shannon described the collector as a "backup" that "should be much faster" [5]. Reference counting already frees dead objects, so the collector only has to find unreachable cycles [5].
The collector reverted from 3.14 got its better ratio from which spaces it scanned [1]. New objects go into fixed-size spaces, and a space can only be scavenged after it fills and closes to new allocations [9]. With the whole heap in one generation, it could scavenge old spaces as well as young ones. Spaces that have existed longer tend to yield more collected objects [16].
On pause times it beat its own brief. The goal was to cut maximum pause times by an order of magnitude on larger heaps [2]. Going from around 3 seconds to tens of milliseconds is a factor of at least 30, even if "tens" means 99 [2].
In Shannon's terms, a generation is a run of consecutive spaces, and spaces move in only one direction through generations [10]. A young generation pays off under the generational hypothesis, that most objects die young, though the exact profile depends on the program [11]. In CPython I'd expect the question to be narrower. Reference counting already frees objects that die outside a cycle, so the young side helps only as much as cyclic garbage also dies young [5].
Other runtimes cannot answer that. Asked by Hood Chatham how Python's effectiveness compares with other collectors, Shannon said a direct comparison would be "unfair", because "nobody else does reference-counting and garbage collection" [12]. The 11.67% comes from a single graph, and the write-up does not name the workload behind it [3].
What to watch
- Pause and memory measurements for the two-generation design on large heaps, set against the 3.13 collector's roughly 3-second peaks and the reverted collector's memory use.
- Which CPython release, if any, adopts the young-and-old incremental collector as its default.