Skip to content

Commit f0dd4df

Browse files
committed
Revert "GH-108362: Incremental GC implementation (GH-108038)"
This reverts commit 36518e6.
1 parent 2afc718 commit f0dd4df

File tree

13 files changed

+392
-647
lines changed

13 files changed

+392
-647
lines changed

Doc/whatsnew/3.13.rst

Lines changed: 0 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -92,10 +92,6 @@ Interpreter improvements:
9292
New Features
9393
============
9494

95-
* The cyclic garbage collector is now incremental.
96-
This means that maximum pause times are reduced,
97-
by an order of magnitude or more for larger heaps.
98-
9995
Improved Error Messages
10096
-----------------------
10197

@@ -105,13 +101,6 @@ Improved Error Messages
105101
variables. See also :ref:`using-on-controlling-color`.
106102
(Contributed by Pablo Galindo Salgado in :gh:`112730`.)
107103

108-
Incremental Garbage Collection
109-
------------------------------
110-
111-
* The cycle garbage collector is now incremental.
112-
This means that maximum pause times are reduced
113-
by an order of magnitude or more for larger heaps.
114-
115104
Other Language Changes
116105
======================
117106

@@ -257,29 +246,6 @@ fractions
257246
sign handling, minimum width and grouping. (Contributed by Mark Dickinson
258247
in :gh:`111320`.)
259248

260-
gc
261-
--
262-
* The cyclic garbage collector is now incremental, which changes the meanings
263-
of the results of :meth:`gc.get_threshold` and :meth:`gc.get_threshold` as
264-
well as :meth:`gc.get_count` and :meth:`gc.get_stats`.
265-
* :meth:`gc.get_threshold` returns a three-tuple for backwards compatibility,
266-
the first value is the threshold for young collections, as before, the second
267-
value determines the rate at which the old collection is scanned; the
268-
default is 10 and higher values mean that the old collection is scanned more slowly.
269-
The third value is meangless and is always zero.
270-
* :meth:`gc.set_threshold` ignores any items after the second.
271-
* :meth:`gc.get_count` and :meth:`gc.get_stats`.
272-
These functions return the same format of results as before.
273-
The only difference is that instead of the results refering to
274-
the young, aging and old generations, the results refer to the
275-
young generation and the aging and collecting spaces of the old generation.
276-
277-
In summary, code that attempted to manipulate the behavior of the cycle GC may
278-
not work as well as intended, but it is very unlikely to harmful.
279-
All other code will work just fine.
280-
Uses should avoid calling :meth:`gc.collect` unless their workload is episodic,
281-
but that has always been the case to some extent.
282-
283249
glob
284250
----
285251

Include/internal/pycore_gc.h

Lines changed: 15 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -88,15 +88,11 @@ static inline void _PyObject_GC_SET_SHARED(PyObject *op) {
8888

8989
/* Bit flags for _gc_prev */
9090
/* Bit 0 is set when tp_finalize is called */
91-
#define _PyGC_PREV_MASK_FINALIZED 1
91+
#define _PyGC_PREV_MASK_FINALIZED (1)
9292
/* Bit 1 is set when the object is in generation which is GCed currently. */
93-
#define _PyGC_PREV_MASK_COLLECTING 2
94-
95-
/* Bit 0 is set if the object belongs to old space 1 */
96-
#define _PyGC_NEXT_MASK_OLD_SPACE_1 1
97-
93+
#define _PyGC_PREV_MASK_COLLECTING (2)
9894
/* The (N-2) most significant bits contain the real address. */
99-
#define _PyGC_PREV_SHIFT 2
95+
#define _PyGC_PREV_SHIFT (2)
10096
#define _PyGC_PREV_MASK (((uintptr_t) -1) << _PyGC_PREV_SHIFT)
10197

10298
/* set for debugging information */
@@ -122,21 +118,18 @@ typedef enum {
122118
// Lowest bit of _gc_next is used for flags only in GC.
123119
// But it is always 0 for normal code.
124120
static inline PyGC_Head* _PyGCHead_NEXT(PyGC_Head *gc) {
125-
uintptr_t next = gc->_gc_next & _PyGC_PREV_MASK;
121+
uintptr_t next = gc->_gc_next;
126122
return (PyGC_Head*)next;
127123
}
128124
static inline void _PyGCHead_SET_NEXT(PyGC_Head *gc, PyGC_Head *next) {
129-
uintptr_t unext = (uintptr_t)next;
130-
assert((unext & ~_PyGC_PREV_MASK) == 0);
131-
gc->_gc_next = (gc->_gc_next & ~_PyGC_PREV_MASK) | unext;
125+
gc->_gc_next = (uintptr_t)next;
132126
}
133127

134128
// Lowest two bits of _gc_prev is used for _PyGC_PREV_MASK_* flags.
135129
static inline PyGC_Head* _PyGCHead_PREV(PyGC_Head *gc) {
136130
uintptr_t prev = (gc->_gc_prev & _PyGC_PREV_MASK);
137131
return (PyGC_Head*)prev;
138132
}
139-
140133
static inline void _PyGCHead_SET_PREV(PyGC_Head *gc, PyGC_Head *prev) {
141134
uintptr_t uprev = (uintptr_t)prev;
142135
assert((uprev & ~_PyGC_PREV_MASK) == 0);
@@ -222,13 +215,6 @@ struct gc_generation {
222215
generations */
223216
};
224217

225-
struct gc_collection_stats {
226-
/* number of collected objects */
227-
Py_ssize_t collected;
228-
/* total number of uncollectable objects (put into gc.garbage) */
229-
Py_ssize_t uncollectable;
230-
};
231-
232218
/* Running stats per generation */
233219
struct gc_generation_stats {
234220
/* total number of collections */
@@ -250,8 +236,8 @@ struct _gc_runtime_state {
250236
int enabled;
251237
int debug;
252238
/* linked lists of container objects */
253-
struct gc_generation young;
254-
struct gc_generation old[2];
239+
struct gc_generation generations[NUM_GENERATIONS];
240+
PyGC_Head *generation0;
255241
/* a permanent generation which won't be collected */
256242
struct gc_generation permanent_generation;
257243
struct gc_generation_stats generation_stats[NUM_GENERATIONS];
@@ -264,20 +250,22 @@ struct _gc_runtime_state {
264250
/* This is the number of objects that survived the last full
265251
collection. It approximates the number of long lived objects
266252
tracked by the GC.
253+
267254
(by "full collection", we mean a collection of the oldest
268255
generation). */
269256
Py_ssize_t long_lived_total;
270-
271-
Py_ssize_t work_to_do;
272-
/* Which of the old spaces is the visited space */
273-
int visited_space;
257+
/* This is the number of objects that survived all "non-full"
258+
collections, and are awaiting to undergo a full collection for
259+
the first time. */
260+
Py_ssize_t long_lived_pending;
274261
};
275262

276263

277264
extern void _PyGC_InitState(struct _gc_runtime_state *);
278265

279-
extern Py_ssize_t _PyGC_Collect(PyThreadState *tstate, int generation, _PyGC_Reason reason);
280-
extern void _PyGC_CollectNoFail(PyThreadState *tstate);
266+
extern Py_ssize_t _PyGC_Collect(PyThreadState *tstate, int generation,
267+
_PyGC_Reason reason);
268+
extern Py_ssize_t _PyGC_CollectNoFail(PyThreadState *tstate);
281269

282270
/* Freeze objects tracked by the GC and ignore them in future collections. */
283271
extern void _PyGC_Freeze(PyInterpreterState *interp);

Include/internal/pycore_object.h

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,19 @@ static inline void _Py_RefcntAdd(PyObject* op, Py_ssize_t n)
125125
}
126126
#define _Py_RefcntAdd(op, n) _Py_RefcntAdd(_PyObject_CAST(op), n)
127127

128-
extern void _Py_SetImmortal(PyObject *op);
128+
static inline void _Py_SetImmortal(PyObject *op)
129+
{
130+
if (op) {
131+
#ifdef Py_GIL_DISABLED
132+
op->ob_tid = _Py_UNOWNED_TID;
133+
op->ob_ref_local = _Py_IMMORTAL_REFCNT_LOCAL;
134+
op->ob_ref_shared = 0;
135+
#else
136+
op->ob_refcnt = _Py_IMMORTAL_REFCNT;
137+
#endif
138+
}
139+
}
140+
#define _Py_SetImmortal(op) _Py_SetImmortal(_PyObject_CAST(op))
129141

130142
// Makes an immortal object mortal again with the specified refcnt. Should only
131143
// be used during runtime finalization.
@@ -313,12 +325,11 @@ static inline void _PyObject_GC_TRACK(
313325
filename, lineno, __func__);
314326

315327
PyInterpreterState *interp = _PyInterpreterState_GET();
316-
PyGC_Head *generation0 = &interp->gc.young.head;
328+
PyGC_Head *generation0 = interp->gc.generation0;
317329
PyGC_Head *last = (PyGC_Head*)(generation0->_gc_prev);
318330
_PyGCHead_SET_NEXT(last, gc);
319331
_PyGCHead_SET_PREV(gc, last);
320332
_PyGCHead_SET_NEXT(gc, generation0);
321-
assert((gc->_gc_next & _PyGC_NEXT_MASK_OLD_SPACE_1) == 0);
322333
generation0->_gc_prev = (uintptr_t)gc;
323334
#endif
324335
}

Include/internal/pycore_runtime_init.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -162,12 +162,12 @@ extern PyTypeObject _PyExc_MemoryError;
162162
}, \
163163
.gc = { \
164164
.enabled = 1, \
165-
.young = { .threshold = 2000, }, \
166-
.old = { \
165+
.generations = { \
166+
/* .head is set in _PyGC_InitState(). */ \
167+
{ .threshold = 700, }, \
168+
{ .threshold = 10, }, \
167169
{ .threshold = 10, }, \
168-
{ .threshold = 0, }, \
169170
}, \
170-
.work_to_do = -5000, \
171171
}, \
172172
.object_state = _py_object_state_INIT(INTERP), \
173173
.dtoa = _dtoa_state_INIT(&(INTERP)), \

Lib/test/test_gc.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -383,11 +383,19 @@ def test_collect_generations(self):
383383
# each call to collect(N)
384384
x = []
385385
gc.collect(0)
386-
# x is now in the old gen
386+
# x is now in gen 1
387387
a, b, c = gc.get_count()
388-
# We don't check a since its exact values depends on
388+
gc.collect(1)
389+
# x is now in gen 2
390+
d, e, f = gc.get_count()
391+
gc.collect(2)
392+
# x is now in gen 3
393+
g, h, i = gc.get_count()
394+
# We don't check a, d, g since their exact values depends on
389395
# internal implementation details of the interpreter.
390396
self.assertEqual((b, c), (1, 0))
397+
self.assertEqual((e, f), (0, 1))
398+
self.assertEqual((h, i), (0, 0))
391399

392400
def test_trashcan(self):
393401
class Ouch:
@@ -838,6 +846,16 @@ def test_get_objects_generations(self):
838846
self.assertFalse(
839847
any(l is element for element in gc.get_objects(generation=2))
840848
)
849+
gc.collect(generation=1)
850+
self.assertFalse(
851+
any(l is element for element in gc.get_objects(generation=0))
852+
)
853+
self.assertFalse(
854+
any(l is element for element in gc.get_objects(generation=1))
855+
)
856+
self.assertTrue(
857+
any(l is element for element in gc.get_objects(generation=2))
858+
)
841859
gc.collect(generation=2)
842860
self.assertFalse(
843861
any(l is element for element in gc.get_objects(generation=0))

Misc/NEWS.d/next/Core and Builtins/2024-01-07-04-22-51.gh-issue-108362.oB9Gcf.rst

Lines changed: 0 additions & 13 deletions
This file was deleted.

Modules/gcmodule.c

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -158,12 +158,17 @@ gc_set_threshold_impl(PyObject *module, int threshold0, int group_right_1,
158158
{
159159
GCState *gcstate = get_gc_state();
160160

161-
gcstate->young.threshold = threshold0;
161+
gcstate->generations[0].threshold = threshold0;
162162
if (group_right_1) {
163-
gcstate->old[0].threshold = threshold1;
163+
gcstate->generations[1].threshold = threshold1;
164164
}
165165
if (group_right_2) {
166-
gcstate->old[1].threshold = threshold2;
166+
gcstate->generations[2].threshold = threshold2;
167+
168+
/* generations higher than 2 get the same threshold */
169+
for (int i = 3; i < NUM_GENERATIONS; i++) {
170+
gcstate->generations[i].threshold = gcstate->generations[2].threshold;
171+
}
167172
}
168173
Py_RETURN_NONE;
169174
}
@@ -180,9 +185,9 @@ gc_get_threshold_impl(PyObject *module)
180185
{
181186
GCState *gcstate = get_gc_state();
182187
return Py_BuildValue("(iii)",
183-
gcstate->young.threshold,
184-
gcstate->old[0].threshold,
185-
0);
188+
gcstate->generations[0].threshold,
189+
gcstate->generations[1].threshold,
190+
gcstate->generations[2].threshold);
186191
}
187192

188193
/*[clinic input]
@@ -197,9 +202,9 @@ gc_get_count_impl(PyObject *module)
197202
{
198203
GCState *gcstate = get_gc_state();
199204
return Py_BuildValue("(iii)",
200-
gcstate->young.count,
201-
gcstate->old[gcstate->visited_space].count,
202-
gcstate->old[gcstate->visited_space^1].count);
205+
gcstate->generations[0].count,
206+
gcstate->generations[1].count,
207+
gcstate->generations[2].count);
203208
}
204209

205210
/*[clinic input]

Objects/object.c

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2387,21 +2387,6 @@ _Py_NewReferenceNoTotal(PyObject *op)
23872387
new_reference(op);
23882388
}
23892389

2390-
void
2391-
_Py_SetImmortal(PyObject *op)
2392-
{
2393-
if (PyObject_IS_GC(op) && _PyObject_GC_IS_TRACKED(op)) {
2394-
_PyObject_GC_UNTRACK(op);
2395-
}
2396-
#ifdef Py_GIL_DISABLED
2397-
op->ob_tid = _Py_UNOWNED_TID;
2398-
op->ob_ref_local = _Py_IMMORTAL_REFCNT_LOCAL;
2399-
op->ob_ref_shared = 0;
2400-
#else
2401-
op->ob_refcnt = _Py_IMMORTAL_REFCNT;
2402-
#endif
2403-
}
2404-
24052390
void
24062391
_Py_ResurrectReference(PyObject *op)
24072392
{

Objects/structseq.c

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -603,9 +603,6 @@ _PyStructSequence_InitBuiltinWithFlags(PyInterpreterState *interp,
603603
PyStructSequence_Desc *desc,
604604
unsigned long tp_flags)
605605
{
606-
if (Py_TYPE(type) == NULL) {
607-
Py_SET_TYPE(type, &PyType_Type);
608-
}
609606
Py_ssize_t n_unnamed_members;
610607
Py_ssize_t n_members = count_members(desc, &n_unnamed_members);
611608
PyMemberDef *members = NULL;
@@ -621,7 +618,7 @@ _PyStructSequence_InitBuiltinWithFlags(PyInterpreterState *interp,
621618
}
622619
initialize_static_fields(type, desc, members, tp_flags);
623620

624-
_Py_SetImmortal((PyObject *)type);
621+
_Py_SetImmortal(type);
625622
}
626623
#ifndef NDEBUG
627624
else {

0 commit comments

Comments
 (0)