Skip to content

Commit 8a3c499

Browse files
authored
GH-108362: Revert "GH-108362: Incremental GC implementation (GH-108038)" (#115132)
Revert "GH-108362: Incremental GC implementation (GH-108038)" This reverts commit 36518e6.
1 parent d0322fd commit 8a3c499

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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy