Skip to content

Commit 1388aff

Browse files
committed
Revert "pythonGH-108362: Incremental Cycle GC (pythonGH-116206)"
This reverts commit 1530932.
1 parent 60a248b commit 1388aff

File tree

10 files changed

+407
-626
lines changed

10 files changed

+407
-626
lines changed

Doc/whatsnew/3.13.rst

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -908,6 +908,7 @@ fractions
908908
(Contributed by Mark Dickinson in :gh:`111320`.)
909909

910910

911+
<<<<<<< HEAD
911912
gc
912913
--
913914

@@ -938,6 +939,31 @@ may not work exactly as intended, but it is very unlikely to be harmful.
938939
All other code will work just fine.
939940

940941

942+
||||||| 15309329b65 (GH-108362: Incremental Cycle GC (GH-116206))
943+
gc
944+
--
945+
946+
* The cyclic garbage collector is now incremental, which changes the meanings
947+
of the results of :meth:`gc.get_threshold` and :meth:`gc.get_threshold` as
948+
well as :meth:`gc.get_count` and :meth:`gc.get_stats`.
949+
* :meth:`gc.get_threshold` returns a three-tuple for backwards compatibility,
950+
the first value is the threshold for young collections, as before, the second
951+
value determines the rate at which the old collection is scanned; the
952+
default is 10 and higher values mean that the old collection is scanned more slowly.
953+
The third value is meangless and is always zero.
954+
* :meth:`gc.set_threshold` ignores any items after the second.
955+
* :meth:`gc.get_count` and :meth:`gc.get_stats`.
956+
These functions return the same format of results as before.
957+
The only difference is that instead of the results refering to
958+
the young, aging and old generations, the results refer to the
959+
young generation and the aging and collecting spaces of the old generation.
960+
961+
In summary, code that attempted to manipulate the behavior of the cycle GC may
962+
not work exactly as intended, but it is very unlikely to harmful.
963+
All other code will work just fine.
964+
965+
=======
966+
>>>>>>> parent of 15309329b65 (GH-108362: Incremental Cycle GC (GH-116206))
941967
glob
942968
----
943969

Include/internal/pycore_gc.h

Lines changed: 12 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -142,14 +142,11 @@ static inline void _PyObject_GC_SET_SHARED_INLINE(PyObject *op) {
142142

143143
/* Bit flags for _gc_prev */
144144
/* Bit 0 is set when tp_finalize is called */
145-
#define _PyGC_PREV_MASK_FINALIZED 1
145+
#define _PyGC_PREV_MASK_FINALIZED (1)
146146
/* Bit 1 is set when the object is in generation which is GCed currently. */
147-
#define _PyGC_PREV_MASK_COLLECTING 2
148-
149-
/* Bit 0 is set if the object belongs to old space 1 */
150-
#define _PyGC_NEXT_MASK_OLD_SPACE_1 1
151-
152-
#define _PyGC_PREV_SHIFT 2
147+
#define _PyGC_PREV_MASK_COLLECTING (2)
148+
/* The (N-2) most significant bits contain the real address. */
149+
#define _PyGC_PREV_SHIFT (2)
153150
#define _PyGC_PREV_MASK (((uintptr_t) -1) << _PyGC_PREV_SHIFT)
154151

155152
/* set for debugging information */
@@ -175,21 +172,18 @@ typedef enum {
175172
// Lowest bit of _gc_next is used for flags only in GC.
176173
// But it is always 0 for normal code.
177174
static inline PyGC_Head* _PyGCHead_NEXT(PyGC_Head *gc) {
178-
uintptr_t next = gc->_gc_next & _PyGC_PREV_MASK;
175+
uintptr_t next = gc->_gc_next;
179176
return (PyGC_Head*)next;
180177
}
181178
static inline void _PyGCHead_SET_NEXT(PyGC_Head *gc, PyGC_Head *next) {
182-
uintptr_t unext = (uintptr_t)next;
183-
assert((unext & ~_PyGC_PREV_MASK) == 0);
184-
gc->_gc_next = (gc->_gc_next & ~_PyGC_PREV_MASK) | unext;
179+
gc->_gc_next = (uintptr_t)next;
185180
}
186181

187182
// Lowest two bits of _gc_prev is used for _PyGC_PREV_MASK_* flags.
188183
static inline PyGC_Head* _PyGCHead_PREV(PyGC_Head *gc) {
189184
uintptr_t prev = (gc->_gc_prev & _PyGC_PREV_MASK);
190185
return (PyGC_Head*)prev;
191186
}
192-
193187
static inline void _PyGCHead_SET_PREV(PyGC_Head *gc, PyGC_Head *prev) {
194188
uintptr_t uprev = (uintptr_t)prev;
195189
assert((uprev & ~_PyGC_PREV_MASK) == 0);
@@ -275,13 +269,6 @@ struct gc_generation {
275269
generations */
276270
};
277271

278-
struct gc_collection_stats {
279-
/* number of collected objects */
280-
Py_ssize_t collected;
281-
/* total number of uncollectable objects (put into gc.garbage) */
282-
Py_ssize_t uncollectable;
283-
};
284-
285272
/* Running stats per generation */
286273
struct gc_generation_stats {
287274
/* total number of collections */
@@ -303,8 +290,8 @@ struct _gc_runtime_state {
303290
int enabled;
304291
int debug;
305292
/* linked lists of container objects */
306-
struct gc_generation young;
307-
struct gc_generation old[2];
293+
struct gc_generation generations[NUM_GENERATIONS];
294+
PyGC_Head *generation0;
308295
/* a permanent generation which won't be collected */
309296
struct gc_generation permanent_generation;
310297
struct gc_generation_stats generation_stats[NUM_GENERATIONS];
@@ -315,11 +302,7 @@ struct _gc_runtime_state {
315302
/* a list of callbacks to be invoked when collection is performed */
316303
PyObject *callbacks;
317304

318-
Py_ssize_t work_to_do;
319-
/* Which of the old spaces is the visited space */
320-
int visited_space;
321-
322-
#ifdef Py_GIL_DISABLED
305+
#ifndef Py_GIL_DISABLED
323306
/* This is the number of objects that survived the last full
324307
collection. It approximates the number of long lived objects
325308
tracked by the GC.
@@ -352,8 +335,9 @@ struct _gc_thread_state {
352335

353336
extern void _PyGC_InitState(struct _gc_runtime_state *);
354337

355-
extern Py_ssize_t _PyGC_Collect(PyThreadState *tstate, int generation, _PyGC_Reason reason);
356-
extern void _PyGC_CollectNoFail(PyThreadState *tstate);
338+
extern Py_ssize_t _PyGC_Collect(PyThreadState *tstate, int generation,
339+
_PyGC_Reason reason);
340+
extern Py_ssize_t _PyGC_CollectNoFail(PyThreadState *tstate);
357341

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

Include/internal/pycore_object.h

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -353,12 +353,11 @@ static inline void _PyObject_GC_TRACK(
353353
filename, lineno, __func__);
354354

355355
PyInterpreterState *interp = _PyInterpreterState_GET();
356-
PyGC_Head *generation0 = &interp->gc.young.head;
356+
PyGC_Head *generation0 = interp->gc.generation0;
357357
PyGC_Head *last = (PyGC_Head*)(generation0->_gc_prev);
358358
_PyGCHead_SET_NEXT(last, gc);
359359
_PyGCHead_SET_PREV(gc, last);
360360
_PyGCHead_SET_NEXT(gc, generation0);
361-
assert((gc->_gc_next & _PyGC_NEXT_MASK_OLD_SPACE_1) == 0);
362361
generation0->_gc_prev = (uintptr_t)gc;
363362
#endif
364363
}

Include/internal/pycore_runtime_init.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -228,12 +228,12 @@ extern PyTypeObject _PyExc_MemoryError;
228228
}, \
229229
.gc = { \
230230
.enabled = 1, \
231-
.young = { .threshold = 2000, }, \
232-
.old = { \
231+
.generations = { \
232+
/* .head is set in _PyGC_InitState(). */ \
233+
{ .threshold = 2000, }, \
234+
{ .threshold = 10, }, \
233235
{ .threshold = 10, }, \
234-
{ .threshold = 0, }, \
235236
}, \
236-
.work_to_do = -5000, \
237237
}, \
238238
.qsbr = { \
239239
.wr_seq = QSBR_INITIAL, \

Lib/test/test_gc.py

Lines changed: 20 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -392,11 +392,19 @@ def test_collect_generations(self):
392392
# each call to collect(N)
393393
x = []
394394
gc.collect(0)
395-
# x is now in the old gen
395+
# x is now in gen 1
396396
a, b, c = gc.get_count()
397-
# We don't check a since its exact values depends on
397+
gc.collect(1)
398+
# x is now in gen 2
399+
d, e, f = gc.get_count()
400+
gc.collect(2)
401+
# x is now in gen 3
402+
g, h, i = gc.get_count()
403+
# We don't check a, d, g since their exact values depends on
398404
# internal implementation details of the interpreter.
399405
self.assertEqual((b, c), (1, 0))
406+
self.assertEqual((e, f), (0, 1))
407+
self.assertEqual((h, i), (0, 0))
400408

401409
def test_trashcan(self):
402410
class Ouch:
@@ -851,6 +859,16 @@ def test_get_objects_generations(self):
851859
self.assertFalse(
852860
any(l is element for element in gc.get_objects(generation=2))
853861
)
862+
gc.collect(generation=1)
863+
self.assertFalse(
864+
any(l is element for element in gc.get_objects(generation=0))
865+
)
866+
self.assertFalse(
867+
any(l is element for element in gc.get_objects(generation=1))
868+
)
869+
self.assertTrue(
870+
any(l is element for element in gc.get_objects(generation=2))
871+
)
854872
gc.collect(generation=2)
855873
self.assertFalse(
856874
any(l is element for element in gc.get_objects(generation=0))
@@ -1088,69 +1106,6 @@ def test_get_referents_on_capsule(self):
10881106
gc.get_referents(tracked_capsule)
10891107

10901108

1091-
1092-
class IncrementalGCTests(unittest.TestCase):
1093-
1094-
def setUp(self):
1095-
# Reenable GC as it is disabled module-wide
1096-
gc.enable()
1097-
1098-
def tearDown(self):
1099-
gc.disable()
1100-
1101-
@requires_gil_enabled("Free threading does not support incremental GC")
1102-
# Use small increments to emulate longer running process in a shorter time
1103-
@gc_threshold(200, 10)
1104-
def test_incremental_gc_handles_fast_cycle_creation(self):
1105-
1106-
class LinkedList:
1107-
1108-
#Use slots to reduce number of implicit objects
1109-
__slots__ = "next", "prev", "surprise"
1110-
1111-
def __init__(self, next=None, prev=None):
1112-
self.next = next
1113-
if next is not None:
1114-
next.prev = self
1115-
self.prev = prev
1116-
if prev is not None:
1117-
prev.next = self
1118-
1119-
def make_ll(depth):
1120-
head = LinkedList()
1121-
for i in range(depth):
1122-
head = LinkedList(head, head.prev)
1123-
return head
1124-
1125-
head = make_ll(10000)
1126-
count = 10000
1127-
1128-
# We expect the counts to go negative eventually
1129-
# as there will some objects we aren't counting,
1130-
# e.g. the gc stats dicts. The test merely checks
1131-
# that the counts don't grow.
1132-
1133-
enabled = gc.isenabled()
1134-
gc.enable()
1135-
olds = []
1136-
for i in range(1000):
1137-
newhead = make_ll(200)
1138-
count += 200
1139-
newhead.surprise = head
1140-
olds.append(newhead)
1141-
if len(olds) == 50:
1142-
stats = gc.get_stats()
1143-
young = stats[0]
1144-
incremental = stats[1]
1145-
old = stats[2]
1146-
collected = young['collected'] + incremental['collected'] + old['collected']
1147-
live = count - collected
1148-
self.assertLess(live, 25000)
1149-
del olds[:]
1150-
if not enabled:
1151-
gc.disable()
1152-
1153-
11541109
class GCCallbackTests(unittest.TestCase):
11551110
def setUp(self):
11561111
# Save gc state and disable it.

Modules/gcmodule.c

Lines changed: 15 additions & 10 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]
@@ -202,14 +207,14 @@ gc_get_count_impl(PyObject *module)
202207
struct _gc_thread_state *gc = &tstate->gc;
203208

204209
// Flush the local allocation count to the global count
205-
_Py_atomic_add_int(&gcstate->young.count, (int)gc->alloc_count);
210+
_Py_atomic_add_int(&gcstate->generations[0].count, (int)gc->alloc_count);
206211
gc->alloc_count = 0;
207212
#endif
208213

209214
return Py_BuildValue("(iii)",
210-
gcstate->young.count,
211-
gcstate->old[gcstate->visited_space].count,
212-
gcstate->old[gcstate->visited_space^1].count);
215+
gcstate->generations[0].count,
216+
gcstate->generations[1].count,
217+
gcstate->generations[2].count);
213218
}
214219

215220
/*[clinic input]

0 commit comments

Comments
 (0)