Skip to content

Commit 0bbd499

Browse files
committed
Forward-port generational GC to 3.15
1 parent e998eb9 commit 0bbd499

File tree

12 files changed

+1054
-2018
lines changed

12 files changed

+1054
-2018
lines changed

Doc/library/gc.rst

Lines changed: 16 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -37,18 +37,11 @@ The :mod:`!gc` module provides the following functions:
3737

3838
.. function:: collect(generation=2)
3939

40-
Perform a collection. The optional argument *generation*
40+
With no arguments, run a full collection. The optional argument *generation*
4141
may be an integer specifying which generation to collect (from 0 to 2). A
4242
:exc:`ValueError` is raised if the generation number is invalid. The sum of
4343
collected objects and uncollectable objects is returned.
4444

45-
Calling ``gc.collect(0)`` will perform a GC collection on the young generation.
46-
47-
Calling ``gc.collect(1)`` will perform a GC collection on the young generation
48-
and an increment of the old generation.
49-
50-
Calling ``gc.collect(2)`` or ``gc.collect()`` performs a full collection
51-
5245
The free lists maintained for a number of built-in types are cleared
5346
whenever a full collection or collection of the highest generation (2)
5447
is run. Not all items in some free lists may be freed due to the
@@ -57,9 +50,6 @@ The :mod:`!gc` module provides the following functions:
5750
The effect of calling ``gc.collect()`` while the interpreter is already
5851
performing a collection is undefined.
5952

60-
.. versionchanged:: 3.14
61-
``generation=1`` performs an increment of collection.
62-
6353

6454
.. function:: set_debug(flags)
6555

@@ -77,18 +67,12 @@ The :mod:`!gc` module provides the following functions:
7767

7868

7969
Returns a list of all objects tracked by the collector, excluding the list
80-
returned. If *generation* is not ``None``, return only the objects as follows:
81-
82-
* 0: All objects in the young generation
83-
* 1: No objects, as there is no generation 1 (as of Python 3.14)
84-
* 2: All objects in the old generation
70+
returned. If *generation* is not ``None``, return only the objects tracked by
71+
the collector that are in that generation.
8572

8673
.. versionchanged:: 3.8
8774
New *generation* parameter.
8875

89-
.. versionchanged:: 3.14
90-
Generation 1 is removed
91-
9276
.. audit-event:: gc.get_objects generation gc.get_objects
9377

9478
.. function:: get_stats()
@@ -124,33 +108,26 @@ The :mod:`!gc` module provides the following functions:
124108
Set the garbage collection thresholds (the collection frequency). Setting
125109
*threshold0* to zero disables collection.
126110

127-
The GC classifies objects into two generations depending on whether they have
128-
survived a collection. New objects are placed in the young generation. If an
129-
object survives a collection it is moved into the old generation.
130-
131-
In order to decide when to run, the collector keeps track of the number of object
111+
The GC classifies objects into three generations depending on how many
112+
collection sweeps they have survived. New objects are placed in the youngest
113+
generation (generation ``0``). If an object survives a collection it is moved
114+
into the next older generation. Since generation ``2`` is the oldest
115+
generation, objects in that generation remain there after a collection. In
116+
order to decide when to run, the collector keeps track of the number object
132117
allocations and deallocations since the last collection. When the number of
133118
allocations minus the number of deallocations exceeds *threshold0*, collection
134-
starts. For each collection, all the objects in the young generation and some
135-
fraction of the old generation is collected.
119+
starts. Initially only generation ``0`` is examined. If generation ``0`` has
120+
been examined more than *threshold1* times since generation ``1`` has been
121+
examined, then generation ``1`` is examined as well.
122+
With the third generation, things are a bit more complicated,
123+
see `Garbage collector design <https://devguide.python.org/garbage_collector>`_
124+
for more information.
136125

137126
In the free-threaded build, the increase in process memory usage is also
138-
checked before running the collector. If the memory usage has not increased
127+
checked before running the collector. If the memory usage has not increased
139128
by 10% since the last collection and the net number of object allocations
140129
has not exceeded 40 times *threshold0*, the collection is not run.
141130

142-
The fraction of the old generation that is collected is **inversely** proportional
143-
to *threshold1*. The larger *threshold1* is, the slower objects in the old generation
144-
are collected.
145-
For the default value of 10, 1% of the old generation is scanned during each collection.
146-
147-
*threshold2* is ignored.
148-
149-
See `Garbage collector design <https://devguide.python.org/garbage_collector>`_ for more information.
150-
151-
.. versionchanged:: 3.14
152-
*threshold2* is ignored
153-
154131

155132
.. function:: get_count()
156133

Include/internal/pycore_gc.h

Lines changed: 4 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -207,10 +207,6 @@ static inline void _PyGC_CLEAR_FINALIZED(PyObject *op) {
207207

208208
extern void _Py_ScheduleGC(PyThreadState *tstate);
209209

210-
#ifndef Py_GIL_DISABLED
211-
extern void _Py_TriggerGC(struct _gc_runtime_state *gcstate);
212-
#endif
213-
214210

215211
/* Tell the GC to track this object.
216212
*
@@ -220,7 +216,7 @@ extern void _Py_TriggerGC(struct _gc_runtime_state *gcstate);
220216
* ob_traverse method.
221217
*
222218
* Internal note: interp->gc.generation0->_gc_prev doesn't have any bit flags
223-
* because it's not object header. So we don't use _PyGCHead_PREV() and
219+
* because it's not an object header. So we don't use _PyGCHead_PREV() and
224220
* _PyGCHead_SET_PREV() for it to avoid unnecessary bitwise operations.
225221
*
226222
* See also the public PyObject_GC_Track() function.
@@ -245,18 +241,12 @@ static inline void _PyObject_GC_TRACK(
245241
filename, lineno, __func__);
246242

247243
struct _gc_runtime_state *gcstate = &_PyInterpreterState_GET()->gc;
248-
PyGC_Head *generation0 = &gcstate->young.head;
249-
PyGC_Head *last = (PyGC_Head*)(generation0->_gc_prev);
244+
PyGC_Head *generation0 = gcstate->generation0;
245+
PyGC_Head *last = (PyGC_Head *)(generation0->_gc_prev);
250246
_PyGCHead_SET_NEXT(last, gc);
251247
_PyGCHead_SET_PREV(gc, last);
252-
uintptr_t not_visited = 1 ^ gcstate->visited_space;
253-
gc->_gc_next = ((uintptr_t)generation0) | not_visited;
248+
_PyGCHead_SET_NEXT(gc, generation0);
254249
generation0->_gc_prev = (uintptr_t)gc;
255-
gcstate->young.count++; /* number of tracked GC objects */
256-
gcstate->heap_size++;
257-
if (gcstate->young.count > gcstate->young.threshold) {
258-
_Py_TriggerGC(gcstate);
259-
}
260250
#endif
261251
}
262252

@@ -291,11 +281,6 @@ static inline void _PyObject_GC_UNTRACK(
291281
_PyGCHead_SET_PREV(next, prev);
292282
gc->_gc_next = 0;
293283
gc->_gc_prev &= _PyGC_PREV_MASK_FINALIZED;
294-
struct _gc_runtime_state *gcstate = &_PyInterpreterState_GET()->gc;
295-
if (gcstate->young.count > 0) {
296-
gcstate->young.count--;
297-
}
298-
gcstate->heap_size--;
299284
#endif
300285
}
301286

Include/internal/pycore_interp_structs.h

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -243,9 +243,16 @@ struct _gc_runtime_state {
243243
/* Is automatic collection enabled? */
244244
int enabled;
245245
int debug;
246-
/* linked lists of container objects */
246+
247+
/* Generational GC state used in GIL builds. */
248+
struct gc_generation generations[NUM_GENERATIONS];
249+
PyGC_Head *generation0;
250+
struct gc_generation_stats generation_stats_gen[NUM_GENERATIONS];
251+
252+
/* Incremental/free-threaded GC state. */
247253
struct gc_generation young;
248254
struct gc_generation old[2];
255+
249256
/* a permanent generation which won't be collected */
250257
struct gc_generation permanent_generation;
251258
struct gc_stats *generation_stats;
@@ -265,7 +272,6 @@ struct _gc_runtime_state {
265272
int visited_space;
266273
int phase;
267274

268-
#ifdef Py_GIL_DISABLED
269275
/* This is the number of objects that survived the last full
270276
collection. It approximates the number of long lived objects
271277
tracked by the GC.
@@ -278,6 +284,7 @@ struct _gc_runtime_state {
278284
the first time. */
279285
Py_ssize_t long_lived_pending;
280286

287+
#ifdef Py_GIL_DISABLED
281288
/* True if gc.freeze() has been used. */
282289
int freeze_active;
283290

Include/internal/pycore_runtime_init.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,11 @@ extern PyTypeObject _PyExc_MemoryError;
130130
}, \
131131
.gc = { \
132132
.enabled = 1, \
133+
.generations = { \
134+
{ .threshold = 2000, }, \
135+
{ .threshold = 10, }, \
136+
{ .threshold = 10, }, \
137+
}, \
133138
.young = { .threshold = 2000, }, \
134139
.old = { \
135140
{ .threshold = 10, }, \

0 commit comments

Comments
 (0)