@@ -1297,6 +1297,7 @@ gc_collect_young(PyThreadState *tstate,
1297
1297
validate_spaces (gcstate );
1298
1298
PyGC_Head * young = & gcstate -> young .head ;
1299
1299
PyGC_Head * visited = & gcstate -> old [gcstate -> visited_space ].head ;
1300
+ untrack_tuples (young );
1300
1301
GC_STAT_ADD (0 , collections , 1 );
1301
1302
#ifdef Py_STATS
1302
1303
{
@@ -1312,30 +1313,10 @@ gc_collect_young(PyThreadState *tstate,
1312
1313
gc_list_init (& survivors );
1313
1314
gc_list_set_space (young , gcstate -> visited_space );
1314
1315
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
1330
1316
gc_list_merge (& survivors , visited );
1331
1317
validate_spaces (gcstate );
1332
1318
gcstate -> young .count = 0 ;
1333
1319
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 ;
1339
1320
add_stats (gcstate , 0 , stats );
1340
1321
validate_spaces (gcstate );
1341
1322
}
@@ -1352,7 +1333,7 @@ IS_IN_VISITED(PyGC_Head *gc, int visited_space)
1352
1333
struct container_and_flag {
1353
1334
PyGC_Head * container ;
1354
1335
int visited_space ;
1355
- uintptr_t size ;
1336
+ intptr_t size ;
1356
1337
};
1357
1338
1358
1339
/* A traversal callback for adding to container) */
@@ -1375,7 +1356,7 @@ visit_add_to_container(PyObject *op, void *arg)
1375
1356
return 0 ;
1376
1357
}
1377
1358
1378
- static uintptr_t
1359
+ static intptr_t
1379
1360
expand_region_transitively_reachable (PyGC_Head * container , PyGC_Head * gc , GCState * gcstate )
1380
1361
{
1381
1362
struct container_and_flag arg = {
@@ -1416,28 +1397,198 @@ completed_cycle(GCState *gcstate)
1416
1397
/* Make sure all objects have visited bit set correctly */
1417
1398
gc_list_set_space (& gcstate -> young .head , not_visited );
1418
1399
gc_list_set_space (& gcstate -> permanent_generation .head , visited );
1419
- gcstate -> work_to_do = 0 ;
1420
1400
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 ;
1421
1562
}
1422
1563
1423
1564
static void
1424
1565
gc_collect_increment (PyThreadState * tstate , struct gc_collection_stats * stats )
1425
1566
{
1426
1567
GC_STAT_ADD (1 , collections , 1 );
1427
1568
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
+ // }
1428
1577
PyGC_Head * not_visited = & gcstate -> old [gcstate -> visited_space ^1 ].head ;
1429
1578
PyGC_Head * visited = & gcstate -> old [gcstate -> visited_space ].head ;
1430
1579
PyGC_Head increment ;
1431
1580
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 ;
1435
1584
}
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 ;
1436
1588
gc_list_set_space (& gcstate -> young .head , gcstate -> visited_space );
1437
1589
gc_list_merge (& gcstate -> young .head , & increment );
1438
- gcstate -> young .count = 0 ;
1439
1590
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 ) ;
1441
1592
while (increment_size < gcstate -> work_to_do ) {
1442
1593
if (gc_list_is_empty (not_visited )) {
1443
1594
break ;
@@ -1449,12 +1600,12 @@ gc_collect_increment(PyThreadState *tstate, struct gc_collection_stats *stats)
1449
1600
gc_set_old_space (gc , gcstate -> visited_space );
1450
1601
increment_size += expand_region_transitively_reachable (& increment , gc , gcstate );
1451
1602
}
1603
+ GC_STAT_ADD (1 , objects_not_transitively_reachable , increment_size );
1452
1604
validate_list (& increment , collecting_clear_unreachable_clear );
1453
1605
gc_list_validate_space (& increment , gcstate -> visited_space );
1454
1606
PyGC_Head survivors ;
1455
1607
gc_list_init (& survivors );
1456
1608
gc_collect_region (tstate , & increment , & survivors , stats );
1457
- gc_list_validate_space (& survivors , gcstate -> visited_space );
1458
1609
gc_list_merge (& survivors , visited );
1459
1610
assert (gc_list_is_empty (& increment ));
1460
1611
gcstate -> work_to_do += gcstate -> heap_size / SCAN_RATE_DIVISOR / scale_factor ;
@@ -1477,6 +1628,7 @@ gc_collect_full(PyThreadState *tstate,
1477
1628
PyGC_Head * young = & gcstate -> young .head ;
1478
1629
PyGC_Head * pending = & gcstate -> old [gcstate -> visited_space ^1 ].head ;
1479
1630
PyGC_Head * visited = & gcstate -> old [gcstate -> visited_space ].head ;
1631
+ untrack_tuples (young );
1480
1632
/* merge all generations into visited */
1481
1633
gc_list_merge (young , pending );
1482
1634
gc_list_validate_space (pending , 1 - gcstate -> visited_space );
0 commit comments