Skip to content

Commit 1d75f5d

Browse files
Forward-port generational GC.
Co-Authored-By: Neil Schemenauer <nas@arctrix.com> Co-Authored-By: Sergey Miryanov <sergey.miryanov@gmail.com>
1 parent d496c63 commit 1d75f5d

6 files changed

Lines changed: 385 additions & 791 deletions

File tree

Include/internal/pycore_gc.h

Lines changed: 4 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,6 @@ static inline void _PyObject_GC_SET_SHARED(PyObject *op) {
131131
* When object are moved from the pending space, old[gcstate->visited_space^1]
132132
* into the increment, the old space bit is flipped.
133133
*/
134-
#define _PyGC_NEXT_MASK_OLD_SPACE_1 1
135134

136135
#define _PyGC_PREV_SHIFT 2
137136
#define _PyGC_PREV_MASK (((uintptr_t) -1) << _PyGC_PREV_SHIFT)
@@ -159,13 +158,11 @@ typedef enum {
159158
// Lowest bit of _gc_next is used for flags only in GC.
160159
// But it is always 0 for normal code.
161160
static inline PyGC_Head* _PyGCHead_NEXT(PyGC_Head *gc) {
162-
uintptr_t next = gc->_gc_next & _PyGC_PREV_MASK;
161+
uintptr_t next = gc->_gc_next;
163162
return (PyGC_Head*)next;
164163
}
165164
static inline void _PyGCHead_SET_NEXT(PyGC_Head *gc, PyGC_Head *next) {
166-
uintptr_t unext = (uintptr_t)next;
167-
assert((unext & ~_PyGC_PREV_MASK) == 0);
168-
gc->_gc_next = (gc->_gc_next & ~_PyGC_PREV_MASK) | unext;
165+
gc->_gc_next = (uintptr_t)next;
169166
}
170167

171168
// Lowest two bits of _gc_prev is used for _PyGC_PREV_MASK_* flags.
@@ -207,10 +204,6 @@ static inline void _PyGC_CLEAR_FINALIZED(PyObject *op) {
207204

208205
extern void _Py_ScheduleGC(PyThreadState *tstate);
209206

210-
#ifndef Py_GIL_DISABLED
211-
extern void _Py_TriggerGC(struct _gc_runtime_state *gcstate);
212-
#endif
213-
214207

215208
/* Tell the GC to track this object.
216209
*
@@ -244,19 +237,12 @@ static inline void _PyObject_GC_TRACK(
244237
"object is in generation which is garbage collected",
245238
filename, lineno, __func__);
246239

247-
struct _gc_runtime_state *gcstate = &_PyInterpreterState_GET()->gc;
248-
PyGC_Head *generation0 = &gcstate->young.head;
240+
PyGC_Head *generation0 = _PyInterpreterState_GET()->gc.generation0;
249241
PyGC_Head *last = (PyGC_Head*)(generation0->_gc_prev);
250242
_PyGCHead_SET_NEXT(last, gc);
251243
_PyGCHead_SET_PREV(gc, last);
252-
uintptr_t not_visited = 1 ^ gcstate->visited_space;
253-
gc->_gc_next = ((uintptr_t)generation0) | not_visited;
244+
_PyGCHead_SET_NEXT(gc, generation0);
254245
generation0->_gc_prev = (uintptr_t)gc;
255-
gcstate->young.count++; /* number of tracked GC objects */
256-
gcstate->heap_size++;
257-
if (gcstate->young.count > gcstate->young.threshold) {
258-
_Py_TriggerGC(gcstate);
259-
}
260246
#endif
261247
}
262248

@@ -291,11 +277,6 @@ static inline void _PyObject_GC_UNTRACK(
291277
_PyGCHead_SET_PREV(next, prev);
292278
gc->_gc_next = 0;
293279
gc->_gc_prev &= _PyGC_PREV_MASK_FINALIZED;
294-
struct _gc_runtime_state *gcstate = &_PyInterpreterState_GET()->gc;
295-
if (gcstate->young.count > 0) {
296-
gcstate->young.count--;
297-
}
298-
gcstate->heap_size--;
299280
#endif
300281
}
301282

Include/internal/pycore_interp_structs.h

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -215,8 +215,13 @@ struct _gc_runtime_state {
215215
int enabled;
216216
int debug;
217217
/* linked lists of container objects */
218+
#ifndef Py_GIL_DISABLED
219+
struct gc_generation generations[NUM_GENERATIONS];
220+
PyGC_Head *generation0;
221+
#else
218222
struct gc_generation young;
219223
struct gc_generation old[2];
224+
#endif
220225
/* a permanent generation which won't be collected */
221226
struct gc_generation permanent_generation;
222227
struct gc_generation_stats generation_stats[NUM_GENERATIONS];
@@ -227,13 +232,6 @@ struct _gc_runtime_state {
227232
/* a list of callbacks to be invoked when collection is performed */
228233
PyObject *callbacks;
229234

230-
Py_ssize_t heap_size;
231-
Py_ssize_t work_to_do;
232-
/* Which of the old spaces is the visited space */
233-
int visited_space;
234-
int phase;
235-
236-
#ifdef Py_GIL_DISABLED
237235
/* This is the number of objects that survived the last full
238236
collection. It approximates the number of long lived objects
239237
tracked by the GC.
@@ -246,6 +244,7 @@ struct _gc_runtime_state {
246244
the first time. */
247245
Py_ssize_t long_lived_pending;
248246

247+
#ifdef Py_GIL_DISABLED
249248
/* True if gc.freeze() has been used. */
250249
int freeze_active;
251250

@@ -261,6 +260,22 @@ struct _gc_runtime_state {
261260
#endif
262261
};
263262

263+
#ifndef Py_GIL_DISABLED
264+
#define GC_GENERATION_INIT \
265+
.generations = { \
266+
{ .threshold = 2000, }, \
267+
{ .threshold = 10, }, \
268+
{ .threshold = 10, }, \
269+
},
270+
#else
271+
#define GC_GENERATION_INIT \
272+
.young = { .threshold = 2000, }, \
273+
.old = { \
274+
{ .threshold = 10, }, \
275+
{ .threshold = 10, }, \
276+
},
277+
#endif
278+
264279
#include "pycore_gil.h" // struct _gil_runtime_state
265280

266281
/**** Import ********/

Include/internal/pycore_runtime_init.h

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -137,13 +137,7 @@ extern PyTypeObject _PyExc_MemoryError;
137137
}, \
138138
.gc = { \
139139
.enabled = 1, \
140-
.young = { .threshold = 2000, }, \
141-
.old = { \
142-
{ .threshold = 10, }, \
143-
{ .threshold = 0, }, \
144-
}, \
145-
.work_to_do = -5000, \
146-
.phase = GC_PHASE_MARK, \
140+
GC_GENERATION_INIT \
147141
}, \
148142
.qsbr = { \
149143
.wr_seq = QSBR_INITIAL, \

Modules/_testinternalcapi.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2346,7 +2346,8 @@ has_deferred_refcount(PyObject *self, PyObject *op)
23462346
static PyObject *
23472347
get_tracked_heap_size(PyObject *self, PyObject *Py_UNUSED(ignored))
23482348
{
2349-
return PyLong_FromInt64(PyInterpreterState_Get()->gc.heap_size);
2349+
// Generational GC doesn't track heap_size, return -1.
2350+
return PyLong_FromInt64(-1);
23502351
}
23512352

23522353
static PyObject *

Modules/gcmodule.c

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -159,13 +159,23 @@ gc_set_threshold_impl(PyObject *module, int threshold0, int group_right_1,
159159
{
160160
GCState *gcstate = get_gc_state();
161161

162+
#ifndef Py_GIL_DISABLED
163+
gcstate->generations[0].threshold = threshold0;
164+
if (group_right_1) {
165+
gcstate->generations[1].threshold = threshold1;
166+
}
167+
if (group_right_2) {
168+
gcstate->generations[2].threshold = threshold2;
169+
}
170+
#else
162171
gcstate->young.threshold = threshold0;
163172
if (group_right_1) {
164173
gcstate->old[0].threshold = threshold1;
165174
}
166175
if (group_right_2) {
167176
gcstate->old[1].threshold = threshold2;
168177
}
178+
#endif
169179
Py_RETURN_NONE;
170180
}
171181

@@ -180,10 +190,17 @@ gc_get_threshold_impl(PyObject *module)
180190
/*[clinic end generated code: output=7902bc9f41ecbbd8 input=286d79918034d6e6]*/
181191
{
182192
GCState *gcstate = get_gc_state();
193+
#ifndef Py_GIL_DISABLED
194+
return Py_BuildValue("(iii)",
195+
gcstate->generations[0].threshold,
196+
gcstate->generations[1].threshold,
197+
gcstate->generations[2].threshold);
198+
#else
183199
return Py_BuildValue("(iii)",
184200
gcstate->young.threshold,
185201
gcstate->old[0].threshold,
186-
0);
202+
gcstate->old[1].threshold);
203+
#endif
187204
}
188205

189206
/*[clinic input]
@@ -207,10 +224,17 @@ gc_get_count_impl(PyObject *module)
207224
gc->alloc_count = 0;
208225
#endif
209226

227+
#ifndef Py_GIL_DISABLED
228+
return Py_BuildValue("(iii)",
229+
gcstate->generations[0].count,
230+
gcstate->generations[1].count,
231+
gcstate->generations[2].count);
232+
#else
210233
return Py_BuildValue("(iii)",
211234
gcstate->young.count,
212-
gcstate->old[gcstate->visited_space].count,
213-
gcstate->old[gcstate->visited_space^1].count);
235+
gcstate->old[0].count,
236+
gcstate->old[1].count);
237+
#endif
214238
}
215239

216240
/*[clinic input]

0 commit comments

Comments
 (0)