Skip to content

Commit bb51b19

Browse files
committed
Apply more diff, but comment out marking step
1 parent 0ac1c07 commit bb51b19

File tree

1 file changed

+181
-29
lines changed

1 file changed

+181
-29
lines changed

Python/gc.c

Lines changed: 181 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1297,6 +1297,7 @@ gc_collect_young(PyThreadState *tstate,
12971297
validate_spaces(gcstate);
12981298
PyGC_Head *young = &gcstate->young.head;
12991299
PyGC_Head *visited = &gcstate->old[gcstate->visited_space].head;
1300+
untrack_tuples(young);
13001301
GC_STAT_ADD(0, collections, 1);
13011302
#ifdef Py_STATS
13021303
{
@@ -1312,30 +1313,10 @@ gc_collect_young(PyThreadState *tstate,
13121313
gc_list_init(&survivors);
13131314
gc_list_set_space(young, gcstate->visited_space);
13141315
gc_collect_region(tstate, young, &survivors, stats);
1315-
Py_ssize_t survivor_count = 0;
1316-
if (gcstate->visited_space) {
1317-
/* objects in visited space have bit set, so we set it here */
1318-
survivor_count = gc_list_set_space(&survivors, 1);
1319-
}
1320-
else {
1321-
PyGC_Head *gc;
1322-
for (gc = GC_NEXT(&survivors); gc != &survivors; gc = GC_NEXT(gc)) {
1323-
#ifdef GC_DEBUG
1324-
assert(gc_old_space(gc) == 0);
1325-
#endif
1326-
survivor_count++;
1327-
}
1328-
}
1329-
(void)survivor_count; // Silence compiler warning
13301316
gc_list_merge(&survivors, visited);
13311317
validate_spaces(gcstate);
13321318
gcstate->young.count = 0;
13331319
gcstate->old[gcstate->visited_space].count++;
1334-
Py_ssize_t scale_factor = gcstate->old[0].threshold;
1335-
if (scale_factor < 1) {
1336-
scale_factor = 1;
1337-
}
1338-
gcstate->work_to_do += gcstate->heap_size / SCAN_RATE_DIVISOR / scale_factor;
13391320
add_stats(gcstate, 0, stats);
13401321
validate_spaces(gcstate);
13411322
}
@@ -1352,7 +1333,7 @@ IS_IN_VISITED(PyGC_Head *gc, int visited_space)
13521333
struct container_and_flag {
13531334
PyGC_Head *container;
13541335
int visited_space;
1355-
uintptr_t size;
1336+
intptr_t size;
13561337
};
13571338

13581339
/* A traversal callback for adding to container) */
@@ -1375,7 +1356,7 @@ visit_add_to_container(PyObject *op, void *arg)
13751356
return 0;
13761357
}
13771358

1378-
static uintptr_t
1359+
static intptr_t
13791360
expand_region_transitively_reachable(PyGC_Head *container, PyGC_Head *gc, GCState *gcstate)
13801361
{
13811362
struct container_and_flag arg = {
@@ -1416,28 +1397,198 @@ completed_cycle(GCState *gcstate)
14161397
/* Make sure all objects have visited bit set correctly */
14171398
gc_list_set_space(&gcstate->young.head, not_visited);
14181399
gc_list_set_space(&gcstate->permanent_generation.head, visited);
1419-
gcstate->work_to_do = 0;
14201400
assert(gc_list_is_empty(&gcstate->old[visited].head));
1401+
gcstate->work_to_do = 0;
1402+
gcstate->phase = GC_PHASE_MARK;
1403+
}
1404+
1405+
static intptr_t
1406+
move_to_reachable(PyObject *op, PyGC_Head *reachable, int visited_space)
1407+
{
1408+
if (op != NULL && !_Py_IsImmortal(op) && _PyObject_IS_GC(op)) {
1409+
PyGC_Head *gc = AS_GC(op);
1410+
if (_PyObject_GC_IS_TRACKED(op) &&
1411+
gc_old_space(gc) != visited_space) {
1412+
gc_flip_old_space(gc);
1413+
gc_list_move(gc, reachable);
1414+
return 1;
1415+
}
1416+
}
1417+
return 0;
1418+
}
1419+
1420+
static intptr_t
1421+
mark_all_reachable(PyGC_Head *reachable, PyGC_Head *visited, int visited_space)
1422+
{
1423+
// Transitively traverse all objects from reachable, until empty
1424+
struct container_and_flag arg = {
1425+
.container = reachable,
1426+
.visited_space = visited_space,
1427+
.size = 0
1428+
};
1429+
while (!gc_list_is_empty(reachable)) {
1430+
PyGC_Head *gc = _PyGCHead_NEXT(reachable);
1431+
assert(gc_old_space(gc) == visited_space);
1432+
gc_list_move(gc, visited);
1433+
PyObject *op = FROM_GC(gc);
1434+
traverseproc traverse = Py_TYPE(op)->tp_traverse;
1435+
(void) traverse(op,
1436+
visit_add_to_container,
1437+
&arg);
1438+
}
1439+
gc_list_validate_space(visited, visited_space);
1440+
return arg.size;
1441+
}
1442+
1443+
static intptr_t
1444+
mark_stacks(PyInterpreterState *interp, PyGC_Head *visited, int visited_space, bool start)
1445+
{
1446+
PyGC_Head reachable;
1447+
gc_list_init(&reachable);
1448+
Py_ssize_t objects_marked = 0;
1449+
// Move all objects on stacks to reachable
1450+
_PyRuntimeState *runtime = &_PyRuntime;
1451+
HEAD_LOCK(runtime);
1452+
PyThreadState* ts = PyInterpreterState_ThreadHead(interp);
1453+
HEAD_UNLOCK(runtime);
1454+
while (ts) {
1455+
_PyInterpreterFrame *frame = ts->current_frame;
1456+
while (frame) {
1457+
if (frame->owner == FRAME_OWNED_BY_CSTACK) {
1458+
frame = frame->previous;
1459+
continue;
1460+
}
1461+
_PyStackRef *locals = frame->localsplus;
1462+
_PyStackRef *sp = frame->stackpointer;
1463+
objects_marked += move_to_reachable(frame->f_locals, &reachable, visited_space);
1464+
PyObject *func = PyStackRef_AsPyObjectBorrow(frame->f_funcobj);
1465+
objects_marked += move_to_reachable(func, &reachable, visited_space);
1466+
while (sp > locals) {
1467+
sp--;
1468+
if (PyStackRef_IsNull(*sp)) {
1469+
continue;
1470+
}
1471+
PyObject *op = PyStackRef_AsPyObjectBorrow(*sp);
1472+
if (!_Py_IsImmortal(op) && _PyObject_IS_GC(op)) {
1473+
PyGC_Head *gc = AS_GC(op);
1474+
if (_PyObject_GC_IS_TRACKED(op) &&
1475+
gc_old_space(gc) != visited_space) {
1476+
gc_flip_old_space(gc);
1477+
objects_marked++;
1478+
gc_list_move(gc, &reachable);
1479+
}
1480+
}
1481+
}
1482+
if (!start && frame->visited) {
1483+
// If this frame has already been visited, then the lower frames
1484+
// will have already been visited and will not have changed
1485+
break;
1486+
}
1487+
frame->visited = 1;
1488+
frame = frame->previous;
1489+
}
1490+
HEAD_LOCK(runtime);
1491+
ts = PyThreadState_Next(ts);
1492+
HEAD_UNLOCK(runtime);
1493+
}
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 intptr_t
1500+
mark_global_roots(PyInterpreterState *interp, PyGC_Head *visited, int visited_space)
1501+
{
1502+
PyGC_Head reachable;
1503+
gc_list_init(&reachable);
1504+
Py_ssize_t objects_marked = 0;
1505+
objects_marked += move_to_reachable(interp->sysdict, &reachable, visited_space);
1506+
objects_marked += move_to_reachable(interp->builtins, &reachable, visited_space);
1507+
objects_marked += move_to_reachable(interp->dict, &reachable, visited_space);
1508+
struct types_state *types = &interp->types;
1509+
for (int i = 0; i < _Py_MAX_MANAGED_STATIC_BUILTIN_TYPES; i++) {
1510+
objects_marked += move_to_reachable(types->builtins.initialized[i].tp_dict, &reachable, visited_space);
1511+
objects_marked += move_to_reachable(types->builtins.initialized[i].tp_subclasses, &reachable, visited_space);
1512+
}
1513+
for (int i = 0; i < _Py_MAX_MANAGED_STATIC_EXT_TYPES; i++) {
1514+
objects_marked += move_to_reachable(types->for_extensions.initialized[i].tp_dict, &reachable, visited_space);
1515+
objects_marked += move_to_reachable(types->for_extensions.initialized[i].tp_subclasses, &reachable, visited_space);
1516+
}
1517+
objects_marked += mark_all_reachable(&reachable, visited, visited_space);
1518+
assert(gc_list_is_empty(&reachable));
1519+
return objects_marked;
1520+
}
1521+
1522+
static intptr_t
1523+
mark_at_start(PyThreadState *tstate)
1524+
{
1525+
// TO DO -- Make this incremental
1526+
GCState *gcstate = &tstate->interp->gc;
1527+
PyGC_Head *visited = &gcstate->old[gcstate->visited_space].head;
1528+
Py_ssize_t objects_marked = mark_global_roots(tstate->interp, visited, gcstate->visited_space);
1529+
objects_marked += mark_stacks(tstate->interp, visited, gcstate->visited_space, true);
1530+
gcstate->work_to_do -= objects_marked;
1531+
gcstate->phase = GC_PHASE_COLLECT;
1532+
validate_spaces(gcstate);
1533+
return objects_marked;
1534+
}
1535+
1536+
static intptr_t
1537+
assess_work_to_do(GCState *gcstate)
1538+
{
1539+
/* The amount of work we want to do depends on three things.
1540+
* 1. The number of new objects created
1541+
* 2. The growth in heap size since the last collection
1542+
* 3. The heap size (up to the number of new objects, to avoid quadratic effects)
1543+
*
1544+
* For a steady state heap, the amount of work to do is three times the number
1545+
* of new objects added to the heap. This ensures that we stay ahead in the
1546+
* worst case of all new objects being garbage.
1547+
*
1548+
* This could be improved by tracking survival rates, but it is still a
1549+
* large improvement on the non-marking approach.
1550+
*/
1551+
Py_ssize_t scale_factor = gcstate->old[0].threshold;
1552+
if (scale_factor < 2) {
1553+
scale_factor = 2;
1554+
}
1555+
Py_ssize_t new_objects = gcstate->young.count;
1556+
Py_ssize_t heap_fraction = gcstate->heap_size / SCAN_RATE_DIVISOR / scale_factor;
1557+
if (heap_fraction > new_objects*2) {
1558+
heap_fraction = new_objects*2;
1559+
}
1560+
gcstate->young.count = 0;
1561+
return new_objects + heap_fraction;
14211562
}
14221563

14231564
static void
14241565
gc_collect_increment(PyThreadState *tstate, struct gc_collection_stats *stats)
14251566
{
14261567
GC_STAT_ADD(1, collections, 1);
14271568
GCState *gcstate = &tstate->interp->gc;
1569+
gcstate->work_to_do += assess_work_to_do(gcstate);
1570+
untrack_tuples(&gcstate->young.head);
1571+
// if (gcstate->phase == GC_PHASE_MARK) {
1572+
// Py_ssize_t objects_marked = mark_at_start(tstate);
1573+
// GC_STAT_ADD(1, objects_transitively_reachable, objects_marked);
1574+
// gcstate->work_to_do -= objects_marked;
1575+
// return;
1576+
// }
14281577
PyGC_Head *not_visited = &gcstate->old[gcstate->visited_space^1].head;
14291578
PyGC_Head *visited = &gcstate->old[gcstate->visited_space].head;
14301579
PyGC_Head increment;
14311580
gc_list_init(&increment);
1432-
Py_ssize_t scale_factor = gcstate->old[0].threshold;
1433-
if (scale_factor < 1) {
1434-
scale_factor = 1;
1581+
int scale_factor = gcstate->old[0].threshold;
1582+
if (scale_factor < 2) {
1583+
scale_factor = 2;
14351584
}
1585+
intptr_t objects_marked = 0; // mark_stacks(tstate->interp, visited, gcstate->visited_space, false);
1586+
GC_STAT_ADD(1, objects_transitively_reachable, objects_marked);
1587+
gcstate->work_to_do -= objects_marked;
14361588
gc_list_set_space(&gcstate->young.head, gcstate->visited_space);
14371589
gc_list_merge(&gcstate->young.head, &increment);
1438-
gcstate->young.count = 0;
14391590
gc_list_validate_space(&increment, gcstate->visited_space);
1440-
Py_ssize_t increment_size = 0;
1591+
Py_ssize_t increment_size = gc_list_size(&increment);
14411592
while (increment_size < gcstate->work_to_do) {
14421593
if (gc_list_is_empty(not_visited)) {
14431594
break;
@@ -1449,12 +1600,12 @@ gc_collect_increment(PyThreadState *tstate, struct gc_collection_stats *stats)
14491600
gc_set_old_space(gc, gcstate->visited_space);
14501601
increment_size += expand_region_transitively_reachable(&increment, gc, gcstate);
14511602
}
1603+
GC_STAT_ADD(1, objects_not_transitively_reachable, increment_size);
14521604
validate_list(&increment, collecting_clear_unreachable_clear);
14531605
gc_list_validate_space(&increment, gcstate->visited_space);
14541606
PyGC_Head survivors;
14551607
gc_list_init(&survivors);
14561608
gc_collect_region(tstate, &increment, &survivors, stats);
1457-
gc_list_validate_space(&survivors, gcstate->visited_space);
14581609
gc_list_merge(&survivors, visited);
14591610
assert(gc_list_is_empty(&increment));
14601611
gcstate->work_to_do += gcstate->heap_size / SCAN_RATE_DIVISOR / scale_factor;
@@ -1477,6 +1628,7 @@ gc_collect_full(PyThreadState *tstate,
14771628
PyGC_Head *young = &gcstate->young.head;
14781629
PyGC_Head *pending = &gcstate->old[gcstate->visited_space^1].head;
14791630
PyGC_Head *visited = &gcstate->old[gcstate->visited_space].head;
1631+
untrack_tuples(young);
14801632
/* merge all generations into visited */
14811633
gc_list_merge(young, pending);
14821634
gc_list_validate_space(pending, 1-gcstate->visited_space);

0 commit comments

Comments
 (0)