Skip to content

Commit 2ec8d8a

Browse files
committed
GC experiment: mark almost all reachable objects before doing collection phase
1 parent bfc1d25 commit 2ec8d8a

File tree

2 files changed

+86
-0
lines changed

2 files changed

+86
-0
lines changed

Include/internal/pycore_gc.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,12 @@ struct gc_generation_stats {
302302
Py_ssize_t uncollectable;
303303
};
304304

305+
enum _GCPhase {
306+
GC_PHASE_DONE = 0,
307+
GC_PHASE_MARK = 1,
308+
GC_PHASE_COLLECT = 2
309+
};
310+
305311
struct _gc_runtime_state {
306312
/* List of objects that still need to be cleaned up, singly linked
307313
* via their gc headers' gc_prev pointers. */
@@ -329,6 +335,7 @@ struct _gc_runtime_state {
329335
Py_ssize_t work_to_do;
330336
/* Which of the old spaces is the visited space */
331337
int visited_space;
338+
int phase;
332339

333340
#ifdef Py_GIL_DISABLED
334341
/* This is the number of objects that survived the last full

Python/gc.c

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1431,13 +1431,92 @@ completed_cycle(GCState *gcstate)
14311431
gc = next;
14321432
}
14331433
gcstate->work_to_do = 0;
1434+
gcstate->phase = GC_PHASE_MARK;
1435+
}
1436+
1437+
static void
1438+
gc_mark(PyThreadState *tstate, struct gc_collection_stats *stats)
1439+
{
1440+
// TO DO -- Make this incremental
1441+
GCState *gcstate = &tstate->interp->gc;
1442+
validate_old(gcstate);
1443+
PyGC_Head *visited = &gcstate->old[gcstate->visited_space].head;
1444+
PyGC_Head reachable;
1445+
gc_list_init(&reachable);
1446+
// Move all reachable objects into visited space.
1447+
PyGC_Head *gc = AS_GC(tstate->interp->sysdict);
1448+
if (gc_old_space(gc) != gcstate->visited_space) {
1449+
gc_flip_old_space(gc);
1450+
gc_list_move(gc, &reachable);
1451+
}
1452+
gc = AS_GC(tstate->interp->builtins);
1453+
if (gc_old_space(gc) != gcstate->visited_space) {
1454+
gc_flip_old_space(gc);
1455+
gc_list_move(gc, &reachable);
1456+
}
1457+
// Move all objects on stacks to reachable
1458+
_PyRuntimeState *runtime = &_PyRuntime;
1459+
HEAD_LOCK(runtime);
1460+
PyThreadState* ts = PyInterpreterState_ThreadHead(tstate->interp);
1461+
HEAD_UNLOCK(runtime);
1462+
Py_ssize_t objects_marked = 0;
1463+
while (ts) {
1464+
_PyInterpreterFrame *frame = ts->current_frame;
1465+
while (frame) {
1466+
_PyStackRef *locals = frame->localsplus;
1467+
_PyStackRef *sp = frame->stackpointer;
1468+
while (sp > locals) {
1469+
sp--;
1470+
if (PyStackRef_IsNull(*sp)) {
1471+
continue;
1472+
}
1473+
PyObject *op = PyStackRef_AsPyObjectBorrow(*sp);
1474+
if (!_Py_IsImmortal(op) && _PyObject_IS_GC(op)) {
1475+
PyGC_Head *gc = AS_GC(op);
1476+
if (_PyObject_GC_IS_TRACKED(op) &&
1477+
gc_old_space(gc) != gcstate->visited_space) {
1478+
gc_flip_old_space(gc);
1479+
objects_marked++;
1480+
gc_list_move(gc, &reachable);
1481+
}
1482+
}
1483+
}
1484+
frame = frame->previous;
1485+
}
1486+
HEAD_LOCK(runtime);
1487+
ts = PyThreadState_Next(ts);
1488+
HEAD_UNLOCK(runtime);
1489+
}
1490+
// Transitively traverse all objects from reachable, until empty
1491+
struct container_and_flag arg = {
1492+
.container = &reachable,
1493+
.visited_space = gcstate->visited_space,
1494+
.size = 0
1495+
};
1496+
while (!gc_list_is_empty(&reachable)) {
1497+
PyGC_Head *gc = _PyGCHead_NEXT(&reachable);
1498+
assert(gc_old_space(gc) == gcstate->visited_space);
1499+
gc_list_move(gc, visited);
1500+
PyObject *op = FROM_GC(gc);
1501+
traverseproc traverse = Py_TYPE(op)->tp_traverse;
1502+
(void) traverse(op,
1503+
visit_add_to_container,
1504+
&arg);
1505+
}
1506+
validate_old(gcstate);
1507+
gcstate->work_to_do -= objects_marked;
1508+
gcstate->phase = GC_PHASE_COLLECT;
14341509
}
14351510

14361511
static void
14371512
gc_collect_increment(PyThreadState *tstate, struct gc_collection_stats *stats)
14381513
{
14391514
GC_STAT_ADD(1, collections, 1);
14401515
GCState *gcstate = &tstate->interp->gc;
1516+
if (gcstate->phase == GC_PHASE_MARK) {
1517+
gc_mark(tstate, stats);
1518+
return;
1519+
}
14411520
PyGC_Head *not_visited = &gcstate->old[gcstate->visited_space^1].head;
14421521
PyGC_Head *visited = &gcstate->old[gcstate->visited_space].head;
14431522
PyGC_Head increment;

0 commit comments

Comments
 (0)