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)
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