Skip to content

Commit 2769005

Browse files
committed
Candidate root GC. Mostly working, but slow.
1 parent 6cfe8e1 commit 2769005

10 files changed

Lines changed: 184 additions & 549 deletions

File tree

Include/internal/pycore_gc.h

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -118,20 +118,6 @@ static inline void _PyObject_GC_SET_SHARED(PyObject *op) {
118118
/* Bit 1 is set when the object is in generation which is GCed currently. */
119119
#define _PyGC_PREV_MASK_COLLECTING ((uintptr_t)2)
120120

121-
/* Bit 0 in _gc_next is the old space bit.
122-
* It is set as follows:
123-
* Young: gcstate->visited_space
124-
* old[0]: 0
125-
* old[1]: 1
126-
* permanent: 0
127-
*
128-
* During a collection all objects handled should have the bit set to
129-
* gcstate->visited_space, as objects are moved from the young gen
130-
* and the increment into old[gcstate->visited_space].
131-
* When object are moved from the pending space, old[gcstate->visited_space^1]
132-
* into the increment, the old space bit is flipped.
133-
*/
134-
#define _PyGC_NEXT_MASK_OLD_SPACE_1 1
135121

136122
#define _PyGC_PREV_SHIFT 2
137123
#define _PyGC_PREV_MASK (((uintptr_t) -1) << _PyGC_PREV_SHIFT)
@@ -239,13 +225,13 @@ static inline void _PyObject_GC_TRACK(
239225
filename, lineno, __func__);
240226

241227
PyInterpreterState *interp = _PyInterpreterState_GET();
242-
PyGC_Head *generation0 = &interp->gc.young.head;
243-
PyGC_Head *last = (PyGC_Head*)(generation0->_gc_prev);
228+
PyGC_Head *live = &interp->gc.live.head;
229+
PyGC_Head *last = (PyGC_Head*)(live->_gc_prev);
244230
_PyGCHead_SET_NEXT(last, gc);
245231
_PyGCHead_SET_PREV(gc, last);
246-
uintptr_t not_visited = 1 ^ interp->gc.visited_space;
247-
gc->_gc_next = ((uintptr_t)generation0) | not_visited;
248-
generation0->_gc_prev = (uintptr_t)gc;
232+
gc->_gc_next = ((uintptr_t)live);
233+
live->_gc_prev = (uintptr_t)gc;
234+
assert((gc->_gc_next & 1) == 0);
249235
#endif
250236
}
251237

Include/internal/pycore_interp_structs.h

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ enum _GCPhase {
200200

201201
/* If we change this, we need to change the default value in the
202202
signature of gc.collect. */
203-
#define NUM_GENERATIONS 3
203+
#define NUM_GENERATIONS 4
204204

205205
struct _gc_runtime_state {
206206
/* List of objects that still need to be cleaned up, singly linked
@@ -213,8 +213,8 @@ struct _gc_runtime_state {
213213
int enabled;
214214
int debug;
215215
/* linked lists of container objects */
216-
struct gc_generation young;
217-
struct gc_generation old[2];
216+
struct gc_generation live;
217+
struct gc_generation candidates[NUM_GENERATIONS];
218218
/* a permanent generation which won't be collected */
219219
struct gc_generation permanent_generation;
220220
struct gc_generation_stats generation_stats[NUM_GENERATIONS];
@@ -227,8 +227,6 @@ struct _gc_runtime_state {
227227

228228
Py_ssize_t heap_size;
229229
Py_ssize_t work_to_do;
230-
/* Which of the old spaces is the visited space */
231-
int visited_space;
232230
int phase;
233231

234232
#ifdef Py_GIL_DISABLED

Include/internal/pycore_object.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,8 @@ _Py_DECREF_CODE(PyCodeObject *co)
436436
#ifndef Py_GIL_DISABLED
437437
#ifdef Py_REF_DEBUG
438438

439+
extern void _Py_CandidateCycleRoot(PyObject *op);
440+
439441
static inline void Py_DECREF_MORTAL(const char *filename, int lineno, PyObject *op)
440442
{
441443
if (op->ob_refcnt <= 0) {
@@ -449,6 +451,9 @@ static inline void Py_DECREF_MORTAL(const char *filename, int lineno, PyObject *
449451
if (--op->ob_refcnt == 0) {
450452
_Py_Dealloc(op);
451453
}
454+
else if (PyObject_GC_IsTracked(op)) {
455+
_Py_CandidateCycleRoot(op);
456+
}
452457
}
453458
#define Py_DECREF_MORTAL(op) Py_DECREF_MORTAL(__FILE__, __LINE__, _PyObject_CAST(op))
454459

Include/internal/pycore_runtime_init.h

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -134,11 +134,7 @@ extern PyTypeObject _PyExc_MemoryError;
134134
}, \
135135
.gc = { \
136136
.enabled = 1, \
137-
.young = { .threshold = 2000, }, \
138-
.old = { \
139-
{ .threshold = 10, }, \
140-
{ .threshold = 0, }, \
141-
}, \
137+
.live = { .threshold = 2000, }, \
142138
.work_to_do = -5000, \
143139
.phase = GC_PHASE_MARK, \
144140
}, \
@@ -233,4 +229,4 @@ extern PyTypeObject _PyExc_MemoryError;
233229
#ifdef __cplusplus
234230
}
235231
#endif
236-
#endif /* !Py_INTERNAL_RUNTIME_INIT_H */
232+
#endif /* !Py_INTERNAL_RUNTIME_INIT_H */

Include/object.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -634,6 +634,11 @@ given type object has a specified feature.
634634
#define _Py_TYPE_REVEALED_FLAG (1 << 3)
635635
#endif
636636

637+
// TO DO: move to private header
638+
#define _Py_GC_OBJECT (1 << 4)
639+
#define _Py_GC_TRACKED (1 << 5)
640+
641+
637642
#define Py_CONSTANT_NONE 0
638643
#define Py_CONSTANT_FALSE 1
639644
#define Py_CONSTANT_TRUE 2

Include/refcount.h

Lines changed: 11 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -314,21 +314,16 @@ PyAPI_FUNC(void) _Py_DecRefSharedDebug(PyObject *, const char *, int);
314314
PyAPI_FUNC(void) _Py_MergeZeroLocalRefcount(PyObject *);
315315
#endif
316316

317-
#if defined(Py_LIMITED_API) && (Py_LIMITED_API+0 >= 0x030c0000 || defined(Py_REF_DEBUG))
318-
// Stable ABI implements Py_DECREF() as a function call on limited C API
319-
// version 3.12 and newer, and on Python built in debug mode. _Py_DecRef() was
320-
// added to Python 3.10.0a7, use Py_DecRef() on older Python versions.
321-
// Py_DecRef() accepts NULL whereas _Py_DecRef() doesn't.
322-
static inline void Py_DECREF(PyObject *op) {
323-
# if Py_LIMITED_API+0 >= 0x030a00A7
324-
_Py_DecRef(op);
325-
# else
326-
Py_DecRef(op);
327-
# endif
328-
}
329-
#define Py_DECREF(op) Py_DECREF(_PyObject_CAST(op))
317+
PyAPI_FUNC(void) Py_DecRefDebug(const char *filename, int lineno,PyObject *);
318+
319+
#ifdef Py_DEBUG
320+
#define Py_DECREF(op) Py_DecRefDebug(__FILE__, __LINE__, _PyObject_CAST(op))
321+
#else
322+
#define Py_DECREF(op) Py_DecRef(_PyObject_CAST(op))
323+
#endif
330324

331-
#elif defined(Py_GIL_DISABLED) && defined(Py_REF_DEBUG)
325+
#ifdef Py_GIL_DISABLED
326+
# ifdef Py_REF_DEBUG
332327
static inline void Py_DECREF(const char *filename, int lineno, PyObject *op)
333328
{
334329
uint32_t local = _Py_atomic_load_uint32_relaxed(&op->ob_ref_local);
@@ -353,8 +348,7 @@ static inline void Py_DECREF(const char *filename, int lineno, PyObject *op)
353348
}
354349
}
355350
#define Py_DECREF(op) Py_DECREF(__FILE__, __LINE__, _PyObject_CAST(op))
356-
357-
#elif defined(Py_GIL_DISABLED)
351+
# else
358352
static inline void Py_DECREF(PyObject *op)
359353
{
360354
uint32_t local = _Py_atomic_load_uint32_relaxed(&op->ob_ref_local);
@@ -375,39 +369,9 @@ static inline void Py_DECREF(PyObject *op)
375369
}
376370
}
377371
#define Py_DECREF(op) Py_DECREF(_PyObject_CAST(op))
378-
379-
#elif defined(Py_REF_DEBUG)
380-
381-
static inline void Py_DECREF(const char *filename, int lineno, PyObject *op)
382-
{
383-
#if SIZEOF_VOID_P > 4
384-
/* If an object has been freed, it will have a negative full refcnt
385-
* If it has not it been freed, will have a very large refcnt */
386-
if (op->ob_refcnt_full <= 0 || op->ob_refcnt > (((PY_UINT32_T)-1) - (1<<20))) {
387-
#else
388-
if (op->ob_refcnt <= 0) {
389-
#endif
390-
_Py_NegativeRefcount(filename, lineno, op);
391-
}
392-
if (_Py_IsImmortal(op)) {
393-
_Py_DECREF_IMMORTAL_STAT_INC();
394-
return;
395-
}
396-
_Py_DECREF_STAT_INC();
397-
_Py_DECREF_DecRefTotal();
398-
if (--op->ob_refcnt == 0) {
399-
_Py_Dealloc(op);
400-
}
401-
}
402-
#define Py_DECREF(op) Py_DECREF(__FILE__, __LINE__, _PyObject_CAST(op))
403-
404-
#else
405-
406-
extern Py_NO_INLINE PyAPI_FUNC(void) Py_DECREF(PyObject *op);
407-
#define Py_DECREF(op) Py_DECREF(_PyObject_CAST(op))
372+
# endif
408373
#endif
409374

410-
411375
/* Safely decref `op` and set `op` to NULL, especially useful in tp_clear
412376
* and tp_dealloc implementations.
413377
*

Modules/gcmodule.c

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

162-
gcstate->young.threshold = threshold0;
162+
gcstate->live.threshold = threshold0;
163163
if (group_right_1) {
164-
gcstate->old[0].threshold = threshold1;
164+
gcstate->candidates[0].threshold = threshold1;
165165
}
166166
if (group_right_2) {
167-
gcstate->old[1].threshold = threshold2;
167+
gcstate->candidates[1].threshold = threshold2;
168168
}
169169
Py_RETURN_NONE;
170170
}
@@ -181,8 +181,8 @@ gc_get_threshold_impl(PyObject *module)
181181
{
182182
GCState *gcstate = get_gc_state();
183183
return Py_BuildValue("(iii)",
184-
gcstate->young.threshold,
185-
gcstate->old[0].threshold,
184+
gcstate->live.threshold,
185+
gcstate->candidates[0].threshold,
186186
0);
187187
}
188188

@@ -203,14 +203,14 @@ gc_get_count_impl(PyObject *module)
203203
struct _gc_thread_state *gc = &tstate->gc;
204204

205205
// Flush the local allocation count to the global count
206-
_Py_atomic_add_int(&gcstate->young.count, (int)gc->alloc_count);
206+
_Py_atomic_add_int(&gcstate->live.count, (int)gc->alloc_count);
207207
gc->alloc_count = 0;
208208
#endif
209209

210210
return Py_BuildValue("(iii)",
211-
gcstate->young.count,
212-
gcstate->old[gcstate->visited_space].count,
213-
gcstate->old[gcstate->visited_space^1].count);
211+
gcstate->live.count,
212+
gcstate->candidates[0].count,
213+
gcstate->candidates[1].count);
214214
}
215215

216216
/*[clinic input]

Objects/object.c

Lines changed: 13 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -264,18 +264,7 @@ _Py_AddToAllObjects(PyObject *op)
264264
}
265265
#endif /* Py_TRACE_REFS */
266266

267-
#undef Py_DECREF_MORTAL
268-
Py_NO_INLINE void Py_DECREF_MORTAL(PyObject *op)
269-
{
270-
assert(!_Py_IsStaticImmortal(op));
271-
_Py_DECREF_STAT_INC();
272-
if (--op->ob_refcnt == 0) {
273-
_Py_Dealloc(op);
274-
}
275-
}
276-
277-
#undef Py_DECREF
278-
Py_NO_INLINE void Py_DECREF(PyObject *op)
267+
Py_NO_INLINE void Py_DecRef(PyObject *op)
279268
{
280269
// Non-limited C API and limited C API for Python 3.9 and older access
281270
// directly PyObject.ob_refcnt.
@@ -284,9 +273,21 @@ Py_NO_INLINE void Py_DECREF(PyObject *op)
284273
return;
285274
}
286275
_Py_DECREF_STAT_INC();
276+
#ifdef Py_REF_DEBUG
277+
_Py_DECREF_DecRefTotal();
278+
#endif
287279
if (--op->ob_refcnt == 0) {
288280
_Py_Dealloc(op);
289281
}
282+
else if (PyObject_GC_IsTracked(op)) {
283+
_Py_CandidateCycleRoot(op);
284+
}
285+
}
286+
287+
void Py_DecRefDebug(const char *filename, int lineno, PyObject *op)
288+
{
289+
// TO DO --implement properly
290+
Py_DecRef(op);
290291
}
291292

292293
#ifdef Py_REF_DEBUG
@@ -361,24 +362,12 @@ Py_IncRef(PyObject *o)
361362
Py_XINCREF(o);
362363
}
363364

364-
void
365-
Py_DecRef(PyObject *o)
366-
{
367-
Py_XDECREF(o);
368-
}
369-
370365
void
371366
_Py_IncRef(PyObject *o)
372367
{
373368
Py_INCREF(o);
374369
}
375370

376-
void
377-
_Py_DecRef(PyObject *o)
378-
{
379-
Py_DECREF(o);
380-
}
381-
382371
#ifdef Py_GIL_DISABLED
383372
# ifdef Py_REF_DEBUG
384373
static int

0 commit comments

Comments
 (0)