Repository navigation
Update GC docs for incremental collection. - #1379
markshannon wants to merge 5 commits into
Conversation
There was a problem hiding this comment.
Thanks for taking the time to do this. A couple of notes:
(Also, @savannahostrowski: this PR diff is a good summary of the differences between the GC implementation you've been working with and the current devguide article, which is outdated.)
|
|
||
|
|
||
| To collect all unreachable cycles in the heap, the garbage collector must scan the | ||
| whole heap. This whole heap scan is called a cycle. |
There was a problem hiding this comment.
Maybe consider a different term here (and throughout below) since "cycle" already has an important meaning here and it can be confusing if we overload it:
| whole heap. This whole heap scan is called a cycle. | |
| whole heap. This whole heap scan is called a full collection. |
There was a problem hiding this comment.
Maybe instead of cycle use "full scavenge".
There was a problem hiding this comment.
A collection is an increment, so that's not a good term.
Cycle is overloaded, so that's not great either.
"Scavenge" is the least ambiguous, but obscure.
Anyone have any other suggestions? I'll go with "scavenge" if not.
| * All any objects reachable from those objects that have not yet been scanned this cycle. | ||
|
|
||
| Any objects surviving this collection are moved to the old generation. | ||
| ollection from cycles. |
There was a problem hiding this comment.
| ollection from cycles. |
| * The oldest fraction of the old generation | ||
| * All any objects reachable from those objects that have not yet been scanned this cycle. | ||
|
|
||
| Any objects surviving this collection are moved to the old generation. |
There was a problem hiding this comment.
Maybe clarify that the objects that started in the old generation are considered "youngest of the old" instead of "oldest of the old" now (there's probably a better way of phrasing it).
There was a problem hiding this comment.
It isn't really the oldest, it is the "least recently scanned". I'll rework this section.
| implementation for the default build uses incremental collection with two | ||
| generations. | ||
|
|
||
| The purpose of generations is to take advantage of what is known as the weak |
There was a problem hiding this comment.
| The purpose of generations is to take advantage of what is known as the weak | |
| Generational garbage collection takes advantage of what is known as the weak |
| two generations: young and old. Every new object starts in the young generation. | ||
|
|
||
|
|
||
| To collect all unreachable cycles in the heap, the garbage collector must scan the |
There was a problem hiding this comment.
Perhaps add detection as distinct from collection.
| To collect all unreachable cycles in the heap, the garbage collector must scan the | |
| To detect and collect all unreachable cycles in the heap, the garbage collector must scan the |
| To collect all unreachable cycles in the heap, the garbage collector must scan the | ||
| whole heap. This whole heap scan is called a cycle. | ||
|
|
||
| In order to limit the time each garbage collection takes, the previous algorithm |
There was a problem hiding this comment.
| In order to limit the time each garbage collection takes, the previous algorithm | |
| To limit the time each garbage collection takes, the detection and collection algorithm |
|
|
||
| * The young generation | ||
| * The oldest fraction of the old generation | ||
| * All any objects reachable from those objects that have not yet been scanned this cycle. |
There was a problem hiding this comment.
| * All any objects reachable from those objects that have not yet been scanned this cycle. | |
| * All objects reachable from those objects that have not yet been scanned this cycle. |
|
|
||
|
|
||
| To collect all unreachable cycles in the heap, the garbage collector must scan the | ||
| whole heap. This whole heap scan is called a cycle. |
There was a problem hiding this comment.
Maybe instead of cycle use "full scavenge".
| When all objects in the heap have been scanned a cycle ends, and all objects are | ||
| considered unscanned again. | ||
|
|
||
| In order to collect all unreachable cycles, each increment must contain all of |
There was a problem hiding this comment.
To make sure that I understand this section on unreachable cycles, I think you're saying that we want to ensure that we fully capture the unreachable cycle because we want to ensure that the cycle is either fully gc'd or not, to avoid partial processing, which could be problematic later on?
If so, maybe it's worth mentioning explicitly.
There was a problem hiding this comment.
If we don't scan the full cycle at once, we cannot collect it. It is otherwise safe.
The old generational collector would scan part cycles all the time; it just delayed the collection of the cycle, at worst until a full collection.
With the incremental collector, if we only scan part of a cycle, it may never be collected. Which would be a problem.
|
Thanks for the comments and suggestions. |
| Survivors are moved to the back of the scanned list. The old part of increment is taken | ||
| from the front of the unscanned list. | ||
|
|
||
| When a cycle starts, no objects in the heap are considered to have been scanned. |
There was a problem hiding this comment.
Using cycle of a "reference cycle" and a "gc execution" it's a bit confusing, can we use other terminology?
There was a problem hiding this comment.
I've used "full scavenge"
| In order to make sure that the whole of any unreachable cycle is contained in an | ||
| increment, all unscanned objects reachable from any object in the increment must | ||
| be included in the increment. | ||
| Thus, to form a complete increment we perform a transitive closure over reachable, |
There was a problem hiding this comment.
As much as I enjoy proper terminology myself. Many people reading this document may not immediately know what a transitive closure is, could you add a brief explanation in parentheses or just explain the process?
|
|
||
| * The young generation | ||
| * The least recently scanned fraction of the old generation. | ||
| * All objects reachable from those objects that have not yet been scanned this cycle. |
There was a problem hiding this comment.
Same as my other comments about "cycle"
| unscanned objects from the initial increment. | ||
| We can exclude scanned objects, as they must have been reachable when scanned. | ||
| If a scanned object becomes part of an unreachable cycle after being scanned, it | ||
| will not be collected this cycle, but it will be collected next cycle. |
There was a problem hiding this comment.
I think this can benefit from a concrete simple example showing how this can happen and how will be eventually cleaned. Either in English or with some diagram or pseudo code
| generations. Every collection operates on the entire heap. | ||
| two generations: young and old. Every new object starts in the young generation. | ||
|
|
||
| To detect and collect all unreachable cycles in the heap, the garbage collector |
There was a problem hiding this comment.
Perhaps use objects instead of cycles here.
| To detect and collect all unreachable cycles in the heap, the garbage collector | |
| To detect and collect all unreachable objects in the heap, the garbage collector |
| of three parts: | ||
|
|
||
| * The young generation | ||
| * The least recently scanned fraction of the old generation. |
There was a problem hiding this comment.
| * The least recently scanned fraction of the old generation. | |
| * The old generation's least recently scanned heap found in the scanned objects list |
|
|
||
| * The young generation | ||
| * The least recently scanned fraction of the old generation. | ||
| * All objects reachable from those objects that have not yet been scanned this cycle. |
There was a problem hiding this comment.
| * All objects reachable from those objects that have not yet been scanned this cycle. | |
| * All objects reachable from those objects that have not yet been scanned. |
| * The least recently scanned fraction of the old generation. | ||
| * All objects reachable from those objects that have not yet been scanned this cycle. | ||
|
|
||
| Any objects surviving this collection are moved to the old generation. |
There was a problem hiding this comment.
| Any objects surviving this collection are moved to the old generation. | |
| Any young generation objects surviving this collection are moved to the old generation, and reachable objects in the old generation remain in the old generation. |
| * All objects reachable from those objects that have not yet been scanned this cycle. | ||
|
|
||
| Any objects surviving this collection are moved to the old generation. | ||
| The old generation is composed of two lists, scanned and unscanned. |
There was a problem hiding this comment.
| The old generation is composed of two lists, scanned and unscanned. | |
| The old generation is composed of two lists: scanned increments and unscanned. |
|
Please note the inconsistency in argument or variable names: This devguide speaks of |
| If a scanned object becomes part of an unreachable cycle after being scanned, it | ||
| will not be collected this cycle, but it will be collected next cycle. | ||
|
|
||
| The GC implementation for the free-threaded build does not use incremental collection. |
There was a problem hiding this comment.
This probably should be called out in an Important block.
|
|
||
| In order to decide when to run, the collector keeps track of the number of object | ||
| allocations and deallocations since the last collection. When the number of | ||
| allocations minus the number of deallocations exceeds ``threshold_0``, |
There was a problem hiding this comment.
| allocations minus the number of deallocations exceeds ``threshold_0``, | |
| allocations minus the number of deallocations exceeds ``threshold0``, |
| collection starts. ``threshold_1`` determines the fraction of the old | ||
| collection that is included in the increment. | ||
| The fraction is inversely proportional to ``threshold_1``, | ||
| as historically a larger ``threshold_1`` meant that old generation | ||
| collections were performed less frequency. | ||
| ``threshold2`` is ignored. |
There was a problem hiding this comment.
Correct as mentioned in @mr-bronson's message. Good catch @mr-bronson. 😄
| collection starts. ``threshold_1`` determines the fraction of the old | |
| collection that is included in the increment. | |
| The fraction is inversely proportional to ``threshold_1``, | |
| as historically a larger ``threshold_1`` meant that old generation | |
| collections were performed less frequency. | |
| ``threshold2`` is ignored. | |
| collection starts. ``threshold1`` determines the fraction of the old | |
| collection that is included in the increment. | |
| The fraction is inversely proportional to ``threshold1``, | |
| as historically a larger ``threshold1`` meant that old generation | |
| collections were performed less frequency. | |
| ``threshold2`` is ignored. |
willingc
left a comment
There was a problem hiding this comment.
Changes look good to me! Thanks @markshannon.
|
Let's hold until we figure out https://discuss.python.org/t/incremental-gc-and-pushing-back-the-3-13-0-release/65285 |
|
Garbage collection doc has been moved to the CPython repo. Unless there is an objection, I recommend closing this devguide issue. |
| The content of these generations can be examined using the | ||
| ``gc.get_objects(generation=NUM)`` function and collections can be triggered | ||
| specifically in a generation by calling ``gc.collect(generation=NUM)``. | ||
| Prior to 3.13, there we three generations. For that reason the |
There was a problem hiding this comment.
| Prior to 3.13, there we three generations. For that reason the | |
| Prior to 3.14, there we three generations. For that reason the |
|
All these docs are now in main |
📚 Documentation preview 📚: https://cpython-devguide--1379.org.readthedocs.build/