@@ -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)
13521333struct  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 
13791360expand_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
14231564static  void 
14241565gc_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