Skip to content

Commit af62957

Browse files
leveretconeyoraluben
authored andcommitted
Backport pythonGH-108362: Incremental GC implementation
Implements incremental cyclic GC. Instead of traversing one generation on each collection, we traverse the young generation and the oldest part of the old generation. By traversing the old generation a chunk at a time, we keep pause times down a lot.
1 parent 7a35fd5 commit af62957

File tree

6 files changed

+538
-324
lines changed

6 files changed

+538
-324
lines changed

Include/cpython/objimpl.h

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ PyAPI_FUNC(void) PyObject_GetArenaAllocator(PyObjectArenaAllocator *allocator);
3333
PyAPI_FUNC(void) PyObject_SetArenaAllocator(PyObjectArenaAllocator *allocator);
3434

3535

36-
PyAPI_FUNC(Py_ssize_t) _PyGC_CollectNoFail(void);
36+
PyAPI_FUNC(void) _PyGC_CollectNoFail(void);
3737
PyAPI_FUNC(Py_ssize_t) _PyGC_CollectIfEnabled(void);
3838

3939

@@ -67,17 +67,25 @@ typedef struct {
6767

6868
/* Bit flags for _gc_prev */
6969
/* Bit 0 is set when tp_finalize is called */
70-
#define _PyGC_PREV_MASK_FINALIZED (1)
70+
#define _PyGC_PREV_MASK_FINALIZED 1
7171
/* Bit 1 is set when the object is in generation which is GCed currently. */
72-
#define _PyGC_PREV_MASK_COLLECTING (2)
72+
#define _PyGC_PREV_MASK_COLLECTING 2
73+
74+
/* Bit 0 is set if the object belongs to old space 1 */
75+
#define _PyGC_NEXT_MASK_OLD_SPACE_1 1
76+
7377
/* The (N-2) most significant bits contain the real address. */
74-
#define _PyGC_PREV_SHIFT (2)
78+
#define _PyGC_PREV_SHIFT 2
7579
#define _PyGC_PREV_MASK (((uintptr_t) -1) << _PyGC_PREV_SHIFT)
7680

7781
// Lowest bit of _gc_next is used for flags only in GC.
7882
// But it is always 0 for normal code.
79-
#define _PyGCHead_NEXT(g) ((PyGC_Head*)(g)->_gc_next)
80-
#define _PyGCHead_SET_NEXT(g, p) ((g)->_gc_next = (uintptr_t)(p))
83+
#define _PyGCHead_NEXT(g) ((PyGC_Head*)((g)->_gc_next & _PyGC_PREV_MASK))
84+
#define _PyGCHead_SET_NEXT(g, p) do { \
85+
assert(((uintptr_t)p & ~_PyGC_PREV_MASK) == 0); \
86+
(g)->_gc_next = ((g)->_gc_next & ~_PyGC_PREV_MASK) \
87+
| ((uintptr_t)(p)); \
88+
} while (0)
8189

8290
// Lowest two bits of _gc_prev is used for _PyGC_PREV_MASK_* flags.
8391
#define _PyGCHead_PREV(g) ((PyGC_Head*)((g)->_gc_prev & _PyGC_PREV_MASK))

Include/internal/pycore_object.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,12 @@ static inline void _PyObject_GC_TRACK_impl(const char *filename, int lineno,
3737
"object is in generation which is garbage collected",
3838
filename, lineno, "_PyObject_GC_TRACK");
3939

40-
PyGC_Head *generation0 = _PyRuntime.gc.generation0;
40+
PyGC_Head *generation0 = &_PyRuntime.gc.young.head;
4141
PyGC_Head *last = (PyGC_Head*)(generation0->_gc_prev);
4242
_PyGCHead_SET_NEXT(last, gc);
4343
_PyGCHead_SET_PREV(gc, last);
4444
_PyGCHead_SET_NEXT(gc, generation0);
45+
assert((gc->_gc_next & _PyGC_NEXT_MASK_OLD_SPACE_1) == 0);
4546
generation0->_gc_prev = (uintptr_t)gc;
4647
}
4748

Include/internal/pycore_pymem.h

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,13 @@ struct gc_generation {
100100
int count; /* count of allocations or collections of younger
101101
generations */
102102
};
103+
104+
struct gc_collection_stats {
105+
/* number of collected objects */
106+
Py_ssize_t collected;
107+
/* total number of uncollectable objects (put into gc.garbage) */
108+
Py_ssize_t uncollectable;
109+
};
103110

104111
/* Running stats per generation */
105112
struct gc_generation_stats {
@@ -121,8 +128,8 @@ struct _gc_runtime_state {
121128
int enabled;
122129
int debug;
123130
/* linked lists of container objects */
124-
struct gc_generation generations[NUM_GENERATIONS];
125-
PyGC_Head *generation0;
131+
struct gc_generation young;
132+
struct gc_generation old[2];
126133
/* a permanent generation which won't be collected */
127134
struct gc_generation permanent_generation;
128135
struct gc_generation_stats generation_stats[NUM_GENERATIONS];
@@ -132,17 +139,10 @@ struct _gc_runtime_state {
132139
PyObject *garbage;
133140
/* a list of callbacks to be invoked when collection is performed */
134141
PyObject *callbacks;
135-
/* This is the number of objects that survived the last full
136-
collection. It approximates the number of long lived objects
137-
tracked by the GC.
138-
139-
(by "full collection", we mean a collection of the oldest
140-
generation). */
141-
Py_ssize_t long_lived_total;
142-
/* This is the number of objects that survived all "non-full"
143-
collections, and are awaiting to undergo a full collection for
144-
the first time. */
145-
Py_ssize_t long_lived_pending;
142+
143+
Py_ssize_t work_to_do;
144+
/* Which of the old spaces is the visited space */
145+
int visited_space;
146146
};
147147

148148
PyAPI_FUNC(void) _PyGC_InitializeRuntime(struct _gc_runtime_state *);

Lib/test/test_gc.py

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -310,19 +310,11 @@ def test_collect_generations(self):
310310
# each call to collect(N)
311311
x = []
312312
gc.collect(0)
313-
# x is now in gen 1
313+
# x is now in the old gen
314314
a, b, c = gc.get_count()
315-
gc.collect(1)
316-
# x is now in gen 2
317-
d, e, f = gc.get_count()
318-
gc.collect(2)
319-
# x is now in gen 3
320-
g, h, i = gc.get_count()
321-
# We don't check a, d, g since their exact values depends on
315+
# We don't check a since its exact values depends on
322316
# internal implementation details of the interpreter.
323317
self.assertEqual((b, c), (1, 0))
324-
self.assertEqual((e, f), (0, 1))
325-
self.assertEqual((h, i), (0, 0))
326318

327319
def test_trashcan(self):
328320
class Ouch:
@@ -789,16 +781,6 @@ def test_get_objects(self):
789781
self.assertFalse(
790782
any(l is element for element in gc.get_objects(generation=2))
791783
)
792-
gc.collect(generation=1)
793-
self.assertFalse(
794-
any(l is element for element in gc.get_objects(generation=0))
795-
)
796-
self.assertFalse(
797-
any(l is element for element in gc.get_objects(generation=1))
798-
)
799-
self.assertTrue(
800-
any(l is element for element in gc.get_objects(generation=2))
801-
)
802784
gc.collect(generation=2)
803785
self.assertFalse(
804786
any(l is element for element in gc.get_objects(generation=0))

0 commit comments

Comments
 (0)