Skip to content

gh-159137: Skip redundant GC resurrection analysis - #159138

Merged
pablogsal merged 1 commit into
python:mainfrom
pablogsal:investigate/gc-pass-fusion-20261010
Oct 10, 2026
Merged

pablogsal merged 1 commit into
python:mainfrom
pablogsal:investigate/gc-pass-fusion-20261010

Conversation

@pablogsal

@pablogsal pablogsal commented Oct 10, 2026 •

Copy link
Copy Markdown
Member

An easy win! :) The GC currently does a second reachability check because callbacks can make objects reachable again. If no weakref callback, finalizer that could run Python code, or debug writer ran, we can reuse the first result and skip that check. The finalization scan still runs. Finished generators, coroutines, and async generators use this path because their finalizers are no-ops. Free-threaded builds also skip the extra stop-the-world pause. The measurements below use a GIL-enabled build and predate the optimization for finished generators.

Microbenchmarks

Here each case uses 100,000 objects or allocation iterations. Values are median CPU milliseconds from six processes per case, with seven samples after two warmups. Explicit collections exclude graph construction.

Workload Before After Less CPU time
Retained lists 1.552 ms 1.565 ms -0.79%
Retained nested tuples 2.548 ms 2.571 ms -0.94%
Unreachable list cycles 4.276 ms 3.867 ms 9.55%
Unreachable dictionary cycles 5.498 ms 3.565 ms 35.16%
Unreachable instance cycles 5.984 ms 5.271 ms 11.92%
Instance cycles with a no-op __del__ 11.785 ms 10.744 ms 8.84%
Cyclic list allocation with automatic GC 9.533 ms 8.456 ms 11.29%
Acyclic dictionary/list allocation with automatic GC 10.603 ms 8.706 ms 17.90%

Macrobenchmarks

A bunch of real programs tht hevily use GC. Sphinx 8.2.3 builds all CPython HTML documentation with one worker and fresh caches: 559 source files, 599 HTML files. Mypy 2.4.0 checks its own mypy and mypyc packages: 346 files, without incremental caches or workers. A separate mypy run uses --no-fast-exit, then explicitly collects its analysis graph.

Values are medians from 10 runs per executable, in baseline/candidate/candidate/baseline order. Application CPU time includes imports and command execution. GC durations come from native gc.get_stats() counters. Cleanup collections are separate from application time.

Workload Before (mean CPU time) After (mean CPU time)
Sphinx 78.275 s 70.560 s
Sphinx, separate cleanup 0.687 s 0.656 s
Mypy 26.059 s 26.072 s
Mypy with --no-fast-exit 27.043 s 26.354 s
Mypy with --no-fast-exit, separate cleanup 1.397 s 1.247 s

Related to #159137.

Comment thread Python/gc.c Outdated

@nascheme nascheme left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! I think we should apply the same kind of optimization to the free-threaded GC. I can do that, unless you want to.

Comment thread Python/gc.c Outdated
@pablogsal
pablogsal force-pushed the investigate/gc-pass-fusion-20261010 branch 2 times, most recently from d46924d to 93b25be Compare October 10, 2026 21:54

@sergey-miryanov sergey-miryanov left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One small nit, otherwise looks good to me!

Comment thread Python/gc.c Outdated
@pablogsal
pablogsal force-pushed the investigate/gc-pass-fusion-20261010 branch from 93b25be to 8a5bd9a Compare October 10, 2026 22:13
@pablogsal
pablogsal enabled auto-merge (squash) October 10, 2026 22:18
@pablogsal
pablogsal merged commit fbbf4f4 into python:main Oct 10, 2026
55 checks passed
@pablogsal
pablogsal deleted the investigate/gc-pass-fusion-20261010 branch October 10, 2026 22:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants