GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — gh-159137: `gc`: Count GC survivors in `untrack_tuples()` instead of walking the young generation again by maurycy · Pull Request #159149 · python/cpython

Conversation

@maurycy

@maurycy maurycy commented Oct 10, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor

Inspired by #159137.

After deduce_unreachable(), gc_collect_main() walks the surviving young generation twice.

untrack_tuples() visits every object:

cpython/Python/gc.c

Lines 664 to 676 in 9028df3

static void
untrack_tuples(PyGC_Head *head)
{
PyGC_Head *gc = GC_NEXT(head);
while (gc != head) {
PyObject *op = FROM_GC(gc);
PyGC_Head *next = GC_NEXT(gc);
if (PyTuple_CheckExact(op)) {
_PyTuple_MaybeUntrack(op);
}
gc = next;
}
}

and then gc_list_size(young) walks the same list again only to update long_lived_pending or long_lived_total:

cpython/Python/gc.c

Lines 1517 to 1535 in 9028df3

untrack_tuples(young);
/* Move reachable objects to next generation. */
if (young != old) {
if (generation == NUM_GENERATIONS - 2) {
gcstate->long_lived_pending += gc_list_size(young);
}
gc_list_merge(young, old);
}
else {
// In Python <= 3.13, we called untrack_dicts(young) here to untrack
// atomic-only dicts (see issue #14775). Python 3.14 removed the lazy
// dict tracking machinery entirely (GH-127010) -- dicts are always
// tracked from creation and never untracked by GC. That way, we don't
// have to restore MAINTAIN_TRACKING across every PyDict_SetItem call
// site; the cost is slightly more work for full collections on dicts
// with only atomic values.
gcstate->long_lived_pending = 0;
gcstate->long_lived_total = gc_list_size(young);
}

So the idea is to untrack_tuples() to return the number of objects that stay tracked, and use it instead of gc_list_size(young).

Microbenchmark

Full collections on a heap that stays alive for the whole run. macOS 27 (M4 Air), release build w/o PGO. Median of 6 processes per binary, interleaved A/B/B/A:

heap main (9028df3) 9028df3 + patch
2M objects, dict + list + tuple per entry 77.0 ms 67.4 ms -12%
2M dicts with atomic values 66.9 ms 59.0 ms -12%
5M objects, dict + list + tuple per entry 201.7 ms 169.6 ms -16%
2M tuples of ints 11.6 ms 11.6 ms 0%

Unfortunately, I don't have access to Linux baremetal at the moment.

Macrobenchmark

Sphinx 8.2.3 building the CPython HTML docs with one worker and fresh doctrees (sphinx-build -b html -j 1 -E), same machine and builds, 6 runs per binary interleaved A/B/B/A. GC time is the sum of duration from gc.get_stats()

main (9028df3) 9028df3 + patch
CPU time 46.00 s 44.37 s -3.5%
GC time 10.07 s 8.63 s -14%
GC time, full collections only 8.66 s 7.26 s -16%
CPU time outside GC 35.93 s 35.74 s -0.5%
Microbenchmark
import gc, sys, time
N = int(sys.argv[1]); kind = sys.argv[2]
gc.disable()
if kind == "mixed":
    keep = [({"a": i, "b": None}, [i, i], (i, i + 1)) for i in range(N // 3)]
elif kind == "tuples":
    keep = [(i, i + 1, i + 2) for i in range(N)]
elif kind == "dicts":
    keep = [{"a": i, "b": "x"} for i in range(N)]
for _ in range(2):
    gc.collect()
ts = []
for _ in range(7):
    t0 = time.perf_counter_ns(); gc.collect(); ts.append(time.perf_counter_ns() - t0)
ts.sort(); print(ts[3] / 1e6)
  • Issue: Speed up garbage collector #159137

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@maurycy
up
e3ee67d
@bedevere-app bedevere-app Bot mentioned this pull request Oct 10, 2026
Speed up garbage collector #159137
Open
@maurycy
news
0665b13
@maurycy
maurycy marked this pull request as ready for review October 10, 2026 21:22
@bedevere-app bedevere-app Bot added the awaiting review label Oct 10, 2026
@maurycy

maurycy commented Oct 10, 2026

Copy link
Copy Markdown
Contributor Author

cc @pablogsal @nascheme

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@pablogsal

pablogsal commented Oct 10, 2026

Copy link
Copy Markdown
Member

This is subset of another PR I m finishing now :)

maurycy reacted with heart emoji
All reactions
  • ❤️ 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@pablogsal pablogsal closed this Oct 10, 2026
@maurycy
maurycy deleted the gc-untrack-count branch October 10, 2026 21:40
@pablogsal

pablogsal commented Oct 10, 2026

Copy link
Copy Markdown
Member

See #159154

maurycy reacted with heart emoji
All reactions
  • ❤️ 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

No reviews

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

awaiting review

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Uh oh!

There was an error while loading. Please reload this page.

2 participants

@maurycy @pablogsal
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.