Skip to content

GH-157724: Fix typos in InternalDocs/garbage_collector.md (and in gc_free_threading.c comments, NO code changes) - #157879

Draft
willy-b wants to merge 2 commits into
python:mainfrom
willy-b:fix-gh-157724-squash
Draft

willy-b wants to merge 2 commits into
python:mainfrom
willy-b:fix-gh-157724-squash

Conversation

@willy-b

@willy-b willy-b commented Sep 21, 2026

Copy link
Copy Markdown
Contributor

(no AI or LLMs were used in discovering the typos or preparing these changes)

  • Typo fixes for a few items in InternalDocs/garbage_collector.md

  • Typo fixes for 2x "contigous" (in comments, in gc_free_threading.c - NO code/functionality changes):

    // a contigous sequence of PyObject pointers, can contain NULLs
    and
    // Called when we have a contigous sequence of PyObject pointers, either

  • Add a note in two places where something is specific to the non-free-threaded build

    • At
      objects that have already been visited once (by unsetting the `PREV_MASK_COLLECTING`
      note "PREV_MASK_COLLECTING" is the name of the flag used in the non-free-threaded build
    • At
      In order to save memory, the two linked list pointers in every object with GC
      , note the "fat pointer"/"tagged pointer" memory saving techniques applied to the two linked list pointers discussed in that section are specific to the non-free-threaded build
  • Propose to remove a seemingly outdated (not applicable to e.g. mark alive phase of free-threaded Python builds), potentially confusing paragraph

    In the free-threaded build ( https://peps.python.org/pep-0703/ , https://peps.python.org/pep-0779/ ) already discussed in many places in this article, the "LIFO stack, of unlimited size"1 mentioned seems to not use the existing pointers on "the objects themselves"2, e.g. in gc_propagate_alive (of which it is said "In most programs, this marks nearly all objects that are not actually unreachable"3) _PyObjectStack_Pop4 and _PyObjectStack_Push5 are used, and the push seems to allocate fresh memory6

    _PyObjectStack_Push(_PyObjectStack *stack, PyObject *obj)
    {
    _PyObjectStackChunk *buf = stack->head;
    if (buf == NULL || buf->n == _Py_OBJECT_STACK_CHUNK_SIZE) {
    buf = _PyObjectStackChunk_New();
    if (buf == NULL) {
    return -1;
    }
    buf->prev = stack->head;
    buf->n = 0;
    stack->head = buf;
    }
    (for one simple case to consider, in the free threaded build with prefetch disabled, the gc_propagate_alive appears to be a DFS of "nearly all objects that are not actually unreachable"3 so would seem to have worst case O(n) additional memory requirements -- contrary to "neither does it in any other way require additional memory proportional to the number of objects"2).

    Thus I propose to remove a paragraph on O(1) space usage, "no additional memory proportional to the number of objects" in its current form as potentially confusing. (

    Pragmatically, it's important to note that no recursion is required by any of this,
    and neither does it in any other way require additional memory proportional to the
    number of objects, number of pointers, or the lengths of pointer chains. Apart from
    `O(1)` storage for internal C needs, the objects themselves contain all the storage
    the GC algorithms require.
    )

    (it might still be descriptive of some configuration of the non-free threaded GC7, to gc.c instead of gc_free_threading.c, only if the doubly linked list pointers used for all traversals in that implementation are built into the objects in advance, with the caveat that adding overhead to each object, instead of externalizing it to a separate stack, still requires memory proportional to the number of objects. Note also comments at

    // The free-threaded GC cost is proportional to the number of objects in
    // the mimalloc GC heap and so we should include the counts for untracked
    , and

    The free-threaded build no longer allocates space for the ``PyGC_Head``
    structure in objects that support cyclic garbage collection. A number of
    other fields and data structures are used as replacements, including
    ``ob_gc_bits``, ``ob_tid``, and mimalloc internal data structures.
    )

    References:

Footnotes

  1. ."LIFO stack, of unlimited size" used during mark alive free threaded build GC phase: https://github.com/python/cpython/blob/5539c2a5437acc4f4719aabac375368e0d310bd9/InternalDocs/garbage_collector.md?plain=1#L536

  2. .vs. "the objects themselves contain all the storage the GC algorithms require" https://github.com/python/cpython/blob/5539c2a5437acc4f4719aabac375368e0d310bd9/InternalDocs/garbage_collector.md?plain=1#L301 (may be true for the non-free threaded build only, for gc.c NOT gc_free_threading.c ; though "neither does it in any other way require additional memory proportional to the number of objects" is still possibly misleading as adding per object overhead still requires memory proportional to the number of objects and is not necessarily more scalable than externalizing that cost to a separate data structure.) 2

  3. ."nearly all objects that are not actually unreachable" are visited during the mark alive free threaded build GC phase: https://github.com/python/cpython/blob/e2ff498cc03886b73fca0429ee745b04646ad8e0/Python/gc_free_threading.c#L1363 2

  4. ._PyObjectStack_Pop is used by gc_propagate_alive, e.g. at https://github.com/python/cpython/blob/e2ff498cc03886b73fca0429ee745b04646ad8e0/Python/gc_free_threading.c#L1346 ; also after the mark alive stage too at e.g. mark_reachable https://github.com/python/cpython/blob/41d09220bda74cda6897a4e807d7af0edaff1fd8/Python/gc_free_threading.c#L1048

  5. ._PyObjectStack_Push is used by gc_propagate_alive, e.g. at https://github.com/python/cpython/blob/e2ff498cc03886b73fca0429ee745b04646ad8e0/Python/gc_free_threading.c#L705 ; also after the mark alive stage too at e.g. visit_clear_unreachable https://github.com/python/cpython/blob/41d09220bda74cda6897a4e807d7af0edaff1fd8/Python/gc_free_threading.c#L1032 .

  6. . _PyObjectStack_Push seems to allocate fresh memory, not reuse pointers already on the "objects themselves": https://github.com/python/cpython/blob/e2ff498cc03886b73fca0429ee745b04646ad8e0/Include/internal/pycore_object_stack.h#L37-L48 ; and see also gc_mark_span_push https://github.com/python/cpython/blob/e2ff498cc03886b73fca0429ee745b04646ad8e0/Python/gc_free_threading.c#L666-L678

  7. in other places in the same section distinctions are made between the free threaded and non-free threaded GCs, e.g. at https://github.com/python/cpython/blob/5539c2a5437acc4f4719aabac375368e0d310bd9/InternalDocs/garbage_collector.md?plain=1#L229 but not for this paragraph

…in gc_free_threading.c comments, NO code changes)

(no AI or LLMs were used in discovering the typos or preparing these changes)

- Typo fixes for a few items in InternalDocs/garbage_collector.md
  - At https://github.com/python/cpython/blob/5539c2a5437acc4f4719aabac375368e0d310bd9/InternalDocs/garbage_collector.md?plain=1#L619
    "6 GB L2" should be "6 MB L2"  (describing AMD Ryzen 5 7600X L2 cache size, see https://web.archive.org/web/20260918004211/https://www.amd.com/en/products/processors/desktops/ryzen/7000-series/amd-ryzen-5-7600x.html for reference)
  - At https://github.com/python/cpython/blob/5539c2a5437acc4f4719aabac375368e0d310bd9/InternalDocs/garbage_collector.md?plain=1#L451
    "<constant number>" should be "constant number" so that it is not hidden in markdown display
  - At https://github.com/python/cpython/blob/5539c2a5437acc4f4719aabac375368e0d310bd9/InternalDocs/garbage_collector.md?plain=1#L536
    "prefech" should be "prefetch" ; I also replaced "LIFO stack, of unlimited size" with "LIFO stack, limited only by available memory" (e.g. https://github.com/python/cpython/blob/a64b7ecba4db5480504a1a83ae6e80c81472fe16/Python/gc_free_threading.c#L1426 , https://github.com/python/cpython/blob/a64b7ecba4db5480504a1a83ae6e80c81472fe16/Python/gc_free_threading.c#L826-L831 , before https://github.com/python/cpython/blob/a64b7ecba4db5480504a1a83ae6e80c81472fe16/Python/gc_free_threading.c#L2116 )

- Typo fixes for 2x "contigous" (in comments, in gc_free_threading.c - NO code/functionality changes):
  https://github.com/python/cpython/blob/a64b7ecba4db5480504a1a83ae6e80c81472fe16/Python/gc_free_threading.c#L579
  and
  https://github.com/python/cpython/blob/a64b7ecba4db5480504a1a83ae6e80c81472fe16/Python/gc_free_threading.c#L749

- Propose to remove a seemingly outdated (not applicable to e.g. mark alive phase of free-threaded Python builds), potentially confusing paragraph

  **In the free-threaded build**  ( https://peps.python.org/pep-0703/ , https://peps.python.org/pep-0779/ )
  already discussed in many places in this article,
  the "LIFO stack, of unlimited size"[^0] mentioned seems to not use the existing pointers on "the objects themselves"[^5],
  e.g. in `gc_propagate_alive` (of which it is said "In most programs, this marks nearly all objects that are not actually unreachable"[^1])
  `_PyObjectStack_Pop`[^2]
  and `_PyObjectStack_Push`[^3]
  are used,
  and the push seems to allocate fresh memory[^4]
  https://github.com/python/cpython/blob/e2ff498cc03886b73fca0429ee745b04646ad8e0/Include/internal/pycore_object_stack.h#L37-L48 (for one simple case to consider, in the free threaded build with prefetch disabled,
  the `gc_propagate_alive` appears to be a DFS of "nearly all objects that are not actually unreachable"[^1] so would seem to have worst case O(n) additional memory requirements -- contrary to "neither does it in any other way require additional memory proportional to the
  number of objects"[^5]).

  Thus I propose to remove a paragraph on O(1) space usage, "no additional memory proportional to the number of objects" in its current form as potentially confusing.
  ( https://github.com/python/cpython/blob/5539c2a5437acc4f4719aabac375368e0d310bd9/InternalDocs/garbage_collector.md?plain=1#L299 )

  (it might still be descriptive of some configuration of the non-free threaded GC[^6], to gc.c instead of gc_free_threading.c,
   only if the doubly linked list pointers used for all traversals in that implementation are built into the objects in advance,
  _with the caveat that adding overhead to each object, instead of externalizing it to a separate stack, still requires memory proportional to the number of objects_.
  Note also comments at https://github.com/python/cpython/blob/a64b7ecba4db5480504a1a83ae6e80c81472fe16/Python/gc_free_threading.c#L1192-L1193 , and

    https://github.com/python/cpython/blob/a64b7ecba4db5480504a1a83ae6e80c81472fe16/Misc/NEWS.d/3.13.0a4.rst?plain=1#L150-L153 )

- Add a note in two places where something is specific to the non-free-threaded build
  - At https://github.com/python/cpython/blob/41d09220bda74cda6897a4e807d7af0edaff1fd8/InternalDocs/garbage_collector.md?plain=1#L285
    note "PREV_MASK_COLLECTING" is the name of the flag used in the non-free-threaded build
  - At https://github.com/python/cpython/blob/41d09220bda74cda6897a4e807d7af0edaff1fd8/InternalDocs/garbage_collector.md?plain=1#L634 ,
    note the "fat pointer"/"tagged pointer" memory saving techniques applied to the two linked list pointers discussed in that section are specific to the non-free-threaded build

  References:
  [^0]: ."LIFO stack, of unlimited size" used during mark alive free threaded build GC phase: https://github.com/python/cpython/blob/5539c2a5437acc4f4719aabac375368e0d310bd9/InternalDocs/garbage_collector.md?plain=1#L536
  [^1]: ."nearly all objects that are not actually unreachable" are visited during the mark alive free threaded build GC phase: https://github.com/python/cpython/blob/e2ff498cc03886b73fca0429ee745b04646ad8e0/Python/gc_free_threading.c#L1363
  [^2]: ._PyObjectStack_Pop  is used by gc_propagate_alive, e.g. at https://github.com/python/cpython/blob/e2ff498cc03886b73fca0429ee745b04646ad8e0/Python/gc_free_threading.c#L1346 ; also after the mark alive stage too at e.g. mark_reachable https://github.com/python/cpython/blob/41d09220bda74cda6897a4e807d7af0edaff1fd8/Python/gc_free_threading.c#L1048
  [^3]: ._PyObjectStack_Push  is used by gc_propagate_alive, e.g. at https://github.com/python/cpython/blob/e2ff498cc03886b73fca0429ee745b04646ad8e0/Python/gc_free_threading.c#L705 ; also after the mark alive stage too at e.g. visit_clear_unreachable https://github.com/python/cpython/blob/41d09220bda74cda6897a4e807d7af0edaff1fd8/Python/gc_free_threading.c#L1032 .
  [^4]: . _PyObjectStack_Push seems to allocate fresh memory, not reuse pointers already on the "objects themselves": https://github.com/python/cpython/blob/e2ff498cc03886b73fca0429ee745b04646ad8e0/Include/internal/pycore_object_stack.h#L37-L48 ; and see also `gc_mark_span_push` https://github.com/python/cpython/blob/e2ff498cc03886b73fca0429ee745b04646ad8e0/Python/gc_free_threading.c#L666-L678
  [^5]: .vs. "the objects themselves contain all the storage the GC algorithms require" https://github.com/python/cpython/blob/5539c2a5437acc4f4719aabac375368e0d310bd9/InternalDocs/garbage_collector.md?plain=1#L301 (may be true for the non-free threaded build only, for gc.c NOT gc_free_threading.c  ; though "neither does it in any other way require additional memory proportional to the number of objects" is still possibly misleading as adding per object overhead still requires memory proportional to the number of objects and is not necessarily more scalable than externalizing that cost to a separate data structure.)
  [^6]:  in other places in the same section distinctions are made between the free threaded and non-free threaded GCs, e.g. at https://github.com/python/cpython/blob/5539c2a5437acc4f4719aabac375368e0d310bd9/InternalDocs/garbage_collector.md?plain=1#L229 but not for this paragraph
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.

1 participant