Pangram verdict · v3.3
We believe that this document is fully human-written
AI likelihood · overall
HumanArticle text · 1,394 words · 5 segments analyzed
April 16, 2026, 6:15pm 1 Python 3.14 shipped with a new incremental garbage collector. However, we’ve had a number of reports of significant memory pressure in production environments.We’ve decided to revert it in both 3.14 and 3.15, and go back to the generational GC from 3.13.3.15 is still in alpha, so such changes are fine. For 3.14, it is unusual for a patch release, but the old GC is a known quantity, the new incremental GC didn’t go through the PEP process, and was rolled back just before the final release of 3.13. We’ve discussed this in the core team and with the Steering Council.If we want to reintroduce the incremental GC for 3.16, it can go through the regular PEP process and be more thoroughly evaluated.Schedules:
3.15: The first beta is scheduled for 2026-05-05, just under three weeks from now. If the revert is ready to release within the next week or so, we can put out an extra alpha 9.
3.14: the next patch release 3.14.5 was planned for 2026-06-09, but we’ll release that early when the revert is ready.
I’ll update this topic and the release PEPs when those dates are known.33 Likes pitrou (Antoine Pitrou)
April 17, 2026, 7:37am 2 Would it be possible to include both GCs and let users choose one at startup, or would that be too costly maintenance-wise?2 Likes hugovk (Hugo van Kemenade)
April 17, 2026, 8:37am 3 It’d be too costly.
Having two GCs in 3.14 but just one in 3.13 and 3.15 would make maintenance harder, and would also be much riskier. This is the sort of thing that would need evaluating by any future PEP.7 Likes tim.one (Tim Peters)
April 17, 2026, 9:22am 4 While it’s been a long time since I actively worked on CPython’s gc, that sounds right to me. It’s delicate code, and parts got much harder to follow when “clever tricks” were used to allow cutting the gc object pre-header struct from 3 members to 2. The simpler we can keep that code, the better for all.Especially in a free-threading world, where corrupted memory due to races is likely to show up billions of cycles later, when gc finally gets around to touching every container object. That’s where memory corruption due to flawed extension modules often showed up even with a GIL.2 Likes storchaka (Serhiy Storchaka)
April 17, 2026, 11:18am 5 But we can have two GCs in both 3.14 and 3.15. The old GC should be default, but the new one can be available as an experimental option.Unless this makes the code much more complicated.2 Likes pitrou (Antoine Pitrou)
April 17, 2026, 12:26pm 6 That’s what I meant as well @nas and I have both created branches with -X flags to toggle between GC versions. While it is doable, maintaining both versions would increase long-term maintenance overhead.
Despite this, I’m +1 to have two versions in 3.16+.4 Likes nas (Neil Schemenauer)
April 17, 2026, 4:19pm 8 In my prototype that allows choosing on startup, the incremental GC adds about 1600 lines of code. It’s not too bad in terms of maintenance since code is quite well separated. Keeping gc_inc.c and gc_gen.c as separate compilation units didn’t cause any measurable performance regression. It is more code and more complication and so just going back to old one is the safe and conservative thing to do.Given our experience with trying to introduce a new GC, we should ideally make a new one opt-in and keep the old as the default (e.g. for the 3.16 release). That’s what Java has done. OTOH, they have a team of research people working on new GCs, we have a couple people tinkering in their spare time. BTW, we do have two GC implementations already, the free-threaded one is basically separate and different.I ran some extra timing benchmarks last night. Based on those, the incremental GC does have smaller maximum GC pause times. So if you care about that, you might prefer that one. The downsides, at least if there is a lot of cyclic garbage being created, is that process memory use can be dramatically higher (5x was the worst case I saw) and runtime is slower (more time spent in GC, longer total execution time). In one run, I had 1.3 ms max pause with incremental GC, 26 ms max pause with generational GC. Peak RSS size was 2.7x with incremental GC though.BTW, Sergey has offered to create a PR to revert to the old GC. I’ll be reviewing and I would guess it won’t take too long since we both made prototypes already.9 Likes pitrou (Antoine Pitrou)
April 17, 2026, 5:08pm 9 FTR, is that on a real-world
workload or on a specifically-designed micro-benchmark that generates tons of cycles?1 Like nas (Neil Schemenauer)
April 17, 2026, 6:37pm 10 It’s not real world, it’s synthetic. And it just creates a lot of cycles. Getting better real-world like benchmarks or more reports from people testing it for real apps would be nice.The pyperformance suite contains basically no interesting benchmarks in terms exercising the cyclic GC in a realistic way (benchmark programs don’t use much memory, or don’t run for very long, or don’t create any reference cycles). It has “gc_collect” and “gc_traverse” but these are micro-benchmarks and not at all realistic. I recently added a new one but this doesn’t create cycles. It is intended to test the overhead of GC while you create a large object graph. If you were tuning solely based on that, your conclusion would be to make it so the GC never runs. No cycles so it’s just all overhead.The most interesting case recently was a Sphinx slowdown. That had the two key features: creating a significant number of container objects and having at least some of those contain reference cycles. That slowdown was resolved.In the absence of realistic benchmarks and real-world reports, I think an extensive set of synthetic benchmarks would be helpful. We can at least confirm that cyclic GC performance doesn’t degrade too much under the range of situations that those benchmarks cover.5 Likes hugovk (Hugo van Kemenade)
April 17, 2026, 7:35pm 11 This is the one that triggered the revert in 3.13: the revert happened a couple of days after that issue was opened. tim.one (Tim Peters)
April 17, 2026, 8:12pm 12 A seemingly eternal problem.
Not optimistic. It’s why I said, in a different topic, that the best we can hope for is to stumble into new heuristics to worm around the real-world pathologies that pop up from time to time. Historically, almost all such cases I dealt with in sorting and pymalloc showed up first on Stack Overflow, with “random” users asking for help with “inexplicable slowdowns”.When I was writing Python’s current sort, the only reports I got of timing results on various platforms came from people running the brief, synthetic, sortperf.py, which was part of the standard distribution at the time. Only one set of results from a real app running real data. They shared the result (“2x faster!”), but could not share their company’s data.Quite recently I all but begged users to report just timing results (no data required) for a proposed change to the sorting algorithm. Your reply was the only one I got - and thank you for that .Similar story for judging the string of collision resolution strategies the dict implementation has tried. Overwhelmingly driven by synthetic inputs, and all but the current strategy (which hasn’t changed in years) were eventually discarded for catastrophic behavior on rare reports from real-life apps. But in that case, it’s provable that “catastrophic” collections of keys always exist - the question is the much subtler one of “but how likely is real-life data to stumble into one?”.Not unique to Python. Sebastian Wild. an academic who co-created the terrific “powersort” merge-ordering heuristic, has reported the extremely poor response to his persistent pleas for “real world data”. Mostly he just attracts contrived bad cases.Whereas I early on opened an issue about seemingly quadratic-time behavior on the main branch. I had no idea gc changes were to blame at the time, and the whittled test case created essentially no cycles. It just provoked the heuristics at the time to run parts of gc far more often than reasonable.Much of sorting has quite predictable worst-case O() behavior, but the gc context is much messier than that.