Skip to content

Commit 3513da2

Browse files
committed
Visit new frames before each increment
1 parent 8bd7606 commit 3513da2

File tree

3 files changed

+111
-57
lines changed

3 files changed

+111
-57
lines changed

Include/internal/pycore_frame.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ typedef struct _PyInterpreterFrame {
7171
_PyStackRef *stackpointer;
7272
uint16_t return_offset; /* Only relevant during a function call */
7373
char owner;
74+
char visited;
7475
/* Locals and stack */
7576
_PyStackRef localsplus[1];
7677
} _PyInterpreterFrame;
@@ -165,6 +166,7 @@ _PyFrame_Initialize(
165166
frame->instr_ptr = _PyCode_CODE(code);
166167
frame->return_offset = 0;
167168
frame->owner = FRAME_OWNED_BY_THREAD;
169+
frame->visited = 0;
168170

169171
for (int i = null_locals_from; i < code->co_nlocalsplus; i++) {
170172
frame->localsplus[i] = PyStackRef_NULL;
@@ -341,6 +343,7 @@ _PyFrame_PushTrampolineUnchecked(PyThreadState *tstate, PyCodeObject *code, int
341343
frame->frame_obj = NULL;
342344
frame->instr_ptr = _PyCode_CODE(code);
343345
frame->owner = FRAME_OWNED_BY_THREAD;
346+
frame->visited = 0;
344347
frame->return_offset = 0;
345348

346349
#ifdef Py_GIL_DISABLED

Include/internal/pycore_object.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -466,8 +466,8 @@ static inline void _PyObject_GC_TRACK(
466466
PyGC_Head *last = (PyGC_Head*)(generation0->_gc_prev);
467467
_PyGCHead_SET_NEXT(last, gc);
468468
_PyGCHead_SET_PREV(gc, last);
469-
/* Young objects will be moved into the visited space during GC, so set the bit here */
470-
gc->_gc_next = ((uintptr_t)generation0) | (uintptr_t)interp->gc.visited_space;
469+
uintptr_t not_visited = 1 ^ interp->gc.visited_space;
470+
gc->_gc_next = ((uintptr_t)generation0) | not_visited;
471471
generation0->_gc_prev = (uintptr_t)gc;
472472
#endif
473473
}

Python/gc.c

Lines changed: 106 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1320,6 +1320,7 @@ gc_collect_young(PyThreadState *tstate,
13201320

13211321
PyGC_Head survivors;
13221322
gc_list_init(&survivors);
1323+
gc_list_set_space(young, gcstate->visited_space);
13231324
gc_collect_region(tstate, young, &survivors, UNTRACK_TUPLES, stats);
13241325
Py_ssize_t survivor_count = 0;
13251326
if (gcstate->visited_space) {
@@ -1407,6 +1408,7 @@ expand_region_transitively_reachable(PyGC_Head *container, PyGC_Head *gc, GCStat
14071408
* have been marked as visited */
14081409
assert(IS_IN_VISITED(gc, gcstate->visited_space));
14091410
PyObject *op = FROM_GC(gc);
1411+
assert(_PyObject_GC_IS_TRACKED(op));
14101412
if (_Py_IsImmortal(op)) {
14111413
PyGC_Head *next = GC_NEXT(gc);
14121414
gc_list_move(gc, &get_gc_state()->permanent_generation.head);
@@ -1426,97 +1428,137 @@ expand_region_transitively_reachable(PyGC_Head *container, PyGC_Head *gc, GCStat
14261428
static void
14271429
completed_cycle(GCState *gcstate)
14281430
{
1429-
#ifdef Py_DEBUG
1430-
PyGC_Head *not_visited = &gcstate->old[gcstate->visited_space^1].head;
1431-
assert(gc_list_is_empty(not_visited));
1432-
#endif
1433-
gcstate->visited_space = flip_old_space(gcstate->visited_space);
1431+
assert(gc_list_is_empty(&gcstate->old[gcstate->visited_space^1].head));
1432+
int not_visited = gcstate->visited_space;
1433+
gcstate->visited_space = flip_old_space(not_visited);
14341434
/* Make sure all young objects have old space bit set correctly */
14351435
PyGC_Head *young = &gcstate->young.head;
14361436
PyGC_Head *gc = GC_NEXT(young);
14371437
while (gc != young) {
14381438
PyGC_Head *next = GC_NEXT(gc);
1439-
gc_set_old_space(gc, gcstate->visited_space);
1439+
gc_set_old_space(gc, not_visited);
14401440
gc = next;
14411441
}
14421442
gcstate->work_to_do = 0;
14431443
gcstate->phase = GC_PHASE_MARK;
14441444
}
14451445

1446-
static void
1447-
gc_mark(PyThreadState *tstate, struct gc_collection_stats *stats)
1446+
static int
1447+
move_to_reachable(PyObject *op, PyGC_Head *reachable, int visited_space)
1448+
{
1449+
if (op != NULL && !_Py_IsImmortal(op) && _PyObject_IS_GC(op)) {
1450+
PyGC_Head *gc = AS_GC(op);
1451+
if (_PyObject_GC_IS_TRACKED(op) &&
1452+
gc_old_space(gc) != visited_space) {
1453+
gc_flip_old_space(gc);
1454+
gc_list_move(gc, reachable);
1455+
return 1;
1456+
}
1457+
}
1458+
return 0;
1459+
}
1460+
1461+
static int
1462+
mark_all_reachable(PyGC_Head *reachable, PyGC_Head *visited, int visited_space)
1463+
{
1464+
// Transitively traverse all objects from reachable, until empty
1465+
struct container_and_flag arg = {
1466+
.container = reachable,
1467+
.visited_space = visited_space,
1468+
.mark = 1,
1469+
.size = 0
1470+
};
1471+
while (!gc_list_is_empty(reachable)) {
1472+
PyGC_Head *gc = _PyGCHead_NEXT(reachable);
1473+
assert(gc_old_space(gc) == visited_space);
1474+
gc_list_move(gc, visited);
1475+
PyObject *op = FROM_GC(gc);
1476+
traverseproc traverse = Py_TYPE(op)->tp_traverse;
1477+
(void) traverse(op,
1478+
visit_add_to_container,
1479+
&arg);
1480+
}
1481+
gc_list_validate_space(visited, visited_space);
1482+
return arg.size;
1483+
}
1484+
1485+
static int
1486+
mark_global_roots(PyInterpreterState *interp, PyGC_Head *visited, int visited_space)
1487+
{
1488+
PyGC_Head reachable;
1489+
gc_list_init(&reachable);
1490+
Py_ssize_t objects_marked = 0;
1491+
objects_marked += move_to_reachable(interp->sysdict, &reachable, visited_space);
1492+
objects_marked += move_to_reachable(interp->builtins, &reachable, visited_space);
1493+
objects_marked += move_to_reachable(interp->dict, &reachable, visited_space);
1494+
objects_marked += mark_all_reachable(&reachable, visited, visited_space);
1495+
assert(gc_list_is_empty(&reachable));
1496+
return objects_marked;
1497+
}
1498+
1499+
static int
1500+
mark_stacks(PyInterpreterState *interp, PyGC_Head *visited, int visited_space, bool start)
14481501
{
1449-
// TO DO -- Make this incremental
1450-
GCState *gcstate = &tstate->interp->gc;
1451-
validate_old(gcstate);
1452-
PyGC_Head *visited = &gcstate->old[gcstate->visited_space].head;
14531502
PyGC_Head reachable;
14541503
gc_list_init(&reachable);
1455-
// Move all reachable objects into visited space.
1456-
PyGC_Head *gc = AS_GC(tstate->interp->sysdict);
14571504
Py_ssize_t objects_marked = 0;
1458-
if (gc_old_space(gc) != gcstate->visited_space) {
1459-
gc_flip_old_space(gc);
1460-
gc_list_move(gc, &reachable);
1461-
objects_marked++;
1462-
}
1463-
gc = AS_GC(tstate->interp->builtins);
1464-
if (gc_old_space(gc) != gcstate->visited_space) {
1465-
gc_flip_old_space(gc);
1466-
gc_list_move(gc, &reachable);
1467-
objects_marked++;
1468-
}
14691505
// Move all objects on stacks to reachable
14701506
_PyRuntimeState *runtime = &_PyRuntime;
14711507
HEAD_LOCK(runtime);
1472-
PyThreadState* ts = PyInterpreterState_ThreadHead(tstate->interp);
1508+
PyThreadState* ts = PyInterpreterState_ThreadHead(interp);
14731509
HEAD_UNLOCK(runtime);
14741510
while (ts) {
14751511
_PyInterpreterFrame *frame = ts->current_frame;
14761512
while (frame) {
14771513
_PyStackRef *locals = frame->localsplus;
14781514
_PyStackRef *sp = frame->stackpointer;
1515+
if (frame->owner != FRAME_OWNED_BY_CSTACK) {
1516+
objects_marked += move_to_reachable(frame->f_locals, &reachable, visited_space);
1517+
PyObject *func = PyStackRef_AsPyObjectBorrow(frame->f_funcobj);
1518+
objects_marked += move_to_reachable(func, &reachable, visited_space);
1519+
}
14791520
while (sp > locals) {
14801521
sp--;
14811522
if (PyStackRef_IsNull(*sp)) {
14821523
continue;
14831524
}
14841525
PyObject *op = PyStackRef_AsPyObjectBorrow(*sp);
14851526
if (!_Py_IsImmortal(op) && _PyObject_IS_GC(op)) {
1486-
PyGC_Head *gc = AS_GC(op);
1527+
PyGC_Head *gc = AS_GC(op);
14871528
if (_PyObject_GC_IS_TRACKED(op) &&
1488-
gc_old_space(gc) != gcstate->visited_space) {
1529+
gc_old_space(gc) != visited_space) {
14891530
gc_flip_old_space(gc);
14901531
objects_marked++;
14911532
gc_list_move(gc, &reachable);
14921533
}
14931534
}
14941535
}
1536+
if (!start && frame->visited) {
1537+
// If this frame has already been visited, then the lower frames
1538+
// will have already been visited and will not have changed
1539+
break;
1540+
}
1541+
frame->visited = 1;
14951542
frame = frame->previous;
14961543
}
14971544
HEAD_LOCK(runtime);
14981545
ts = PyThreadState_Next(ts);
14991546
HEAD_UNLOCK(runtime);
15001547
}
1501-
// Transitively traverse all objects from reachable, until empty
1502-
struct container_and_flag arg = {
1503-
.container = &reachable,
1504-
.visited_space = gcstate->visited_space,
1505-
.mark = 1,
1506-
.size = 0
1507-
};
1508-
while (!gc_list_is_empty(&reachable)) {
1509-
PyGC_Head *gc = _PyGCHead_NEXT(&reachable);
1510-
assert(gc_old_space(gc) == gcstate->visited_space);
1511-
gc_list_move(gc, visited);
1512-
PyObject *op = FROM_GC(gc);
1513-
traverseproc traverse = Py_TYPE(op)->tp_traverse;
1514-
(void) traverse(op,
1515-
visit_add_to_container,
1516-
&arg);
1517-
}
1518-
objects_marked += arg.size;
1548+
objects_marked += mark_all_reachable(&reachable, visited, visited_space);
1549+
assert(gc_list_is_empty(&reachable));
1550+
return objects_marked;
1551+
}
1552+
1553+
static void
1554+
mark_at_start(PyThreadState *tstate)
1555+
{
1556+
// TO DO -- Make this incremental
1557+
GCState *gcstate = &tstate->interp->gc;
15191558
validate_old(gcstate);
1559+
PyGC_Head *visited = &gcstate->old[gcstate->visited_space].head;
1560+
Py_ssize_t objects_marked = mark_global_roots(tstate->interp, visited, gcstate->visited_space);
1561+
objects_marked += mark_stacks(tstate->interp, visited, gcstate->visited_space, true);
15201562
gcstate->work_to_do -= objects_marked;
15211563
#ifdef Py_STATS
15221564
if (_Py_stats) {
@@ -1531,8 +1573,9 @@ gc_collect_increment(PyThreadState *tstate, struct gc_collection_stats *stats)
15311573
{
15321574
GC_STAT_ADD(1, collections, 1);
15331575
GCState *gcstate = &tstate->interp->gc;
1576+
gcstate->work_to_do += gcstate->young.count;
15341577
if (gcstate->phase == GC_PHASE_MARK) {
1535-
gc_mark(tstate, stats);
1578+
mark_at_start(tstate);
15361579
return;
15371580
}
15381581
PyGC_Head *not_visited = &gcstate->old[gcstate->visited_space^1].head;
@@ -1543,6 +1586,8 @@ gc_collect_increment(PyThreadState *tstate, struct gc_collection_stats *stats)
15431586
if (scale_factor < 1) {
15441587
scale_factor = 1;
15451588
}
1589+
gcstate->work_to_do -= mark_stacks(tstate->interp, visited, gcstate->visited_space, false);
1590+
gc_list_set_space(&gcstate->young.head, gcstate->visited_space);
15461591
gc_list_merge(&gcstate->young.head, &increment);
15471592
gcstate->young.count = 0;
15481593
gc_list_validate_space(&increment, gcstate->visited_space);
@@ -1554,6 +1599,7 @@ gc_collect_increment(PyThreadState *tstate, struct gc_collection_stats *stats)
15541599
PyGC_Head *gc = _PyGCHead_NEXT(not_visited);
15551600
gc_list_move(gc, &increment);
15561601
increment_size++;
1602+
assert(!_Py_IsImmortal(FROM_GC(gc)));
15571603
gc_set_old_space(gc, gcstate->visited_space);
15581604
increment_size += expand_region_transitively_reachable(&increment, gc, gcstate);
15591605
}
@@ -1564,7 +1610,7 @@ gc_collect_increment(PyThreadState *tstate, struct gc_collection_stats *stats)
15641610
gc_list_validate_space(&survivors, gcstate->visited_space);
15651611
gc_list_merge(&survivors, visited);
15661612
assert(gc_list_is_empty(&increment));
1567-
gcstate->work_to_do += gcstate->heap_size / SCAN_RATE_DIVISOR / scale_factor;
1613+
gcstate->work_to_do += gcstate->heap_size / SCAN_RATE_DIVISOR/ 2 / scale_factor;
15681614
gcstate->work_to_do -= increment_size;
15691615

15701616
validate_old(gcstate);
@@ -1585,20 +1631,25 @@ gc_collect_full(PyThreadState *tstate,
15851631
PyGC_Head *young = &gcstate->young.head;
15861632
PyGC_Head *pending = &gcstate->old[gcstate->visited_space^1].head;
15871633
PyGC_Head *visited = &gcstate->old[gcstate->visited_space].head;
1588-
/* merge all generations into visited */
1589-
gc_list_validate_space(young, gcstate->visited_space);
1590-
gc_list_set_space(pending, gcstate->visited_space);
1634+
mark_global_roots(tstate->interp, visited, gcstate->visited_space);
1635+
mark_stacks(tstate->interp, visited, gcstate->visited_space, true);
1636+
/* merge all generations into pending */
1637+
gc_list_validate_space(young, 1-gcstate->visited_space);
15911638
gc_list_merge(young, pending);
1639+
gc_list_set_space(visited, 1-gcstate->visited_space);
1640+
gc_list_merge(visited, pending);
1641+
/* Mark reachable */
1642+
mark_global_roots(tstate->interp, visited, gcstate->visited_space);
1643+
mark_stacks(tstate->interp, visited, gcstate->visited_space, true);
15921644
gcstate->young.count = 0;
1593-
gc_list_merge(pending, visited);
1594-
1595-
gc_collect_region(tstate, visited, visited,
1645+
gc_list_set_space(pending, gcstate->visited_space);
1646+
gc_collect_region(tstate, pending, visited,
15961647
UNTRACK_TUPLES | UNTRACK_DICTS,
15971648
stats);
15981649
gcstate->young.count = 0;
15991650
gcstate->old[0].count = 0;
16001651
gcstate->old[1].count = 0;
1601-
1652+
completed_cycle(gcstate);
16021653
gcstate->work_to_do = - gcstate->young.threshold * 2;
16031654
_PyGC_ClearAllFreeLists(tstate->interp);
16041655
validate_old(gcstate);

0 commit comments

Comments
 (0)