@@ -185,6 +185,7 @@ _PyGC_Init(PyInterpreterState *interp)
185185 if (gcstate -> callbacks == NULL ) {
186186 return _PyStatus_NO_MEMORY ();
187187 }
188+ gcstate -> prior_heap_size = 0 ;
188189 gcstate -> heap_size = 0 ;
189190
190191 return _PyStatus_OK ();
@@ -1330,16 +1331,11 @@ gc_collect_young(PyThreadState *tstate,
13301331 survivor_count ++ ;
13311332 }
13321333 }
1333- (void )survivor_count ; // Silence compiler warning
13341334 gc_list_merge (& survivors , visited );
13351335 validate_old (gcstate );
13361336 gcstate -> young .count = 0 ;
13371337 gcstate -> old [gcstate -> visited_space ].count ++ ;
1338- Py_ssize_t scale_factor = gcstate -> old [0 ].threshold ;
1339- if (scale_factor < 1 ) {
1340- scale_factor = 1 ;
1341- }
1342- gcstate -> work_to_do += gcstate -> heap_size / SCAN_RATE_DIVISOR / scale_factor ;
1338+ gcstate -> work_to_do += survivor_count * 4 ;
13431339 add_stats (gcstate , 0 , stats );
13441340}
13451341
@@ -1559,13 +1555,43 @@ mark_at_start(PyThreadState *tstate)
15591555 return objects_marked ;
15601556}
15611557
1558+ static Py_ssize_t
1559+ assess_work_to_do (GCState * gcstate )
1560+ {
1561+ /* The amount of work we want to do depends on two things.
1562+ * 1. The number of new objects created
1563+ * 2. The growth in heap size since the last collection
1564+ * 3. The heap size (up to the number of new objects, to avoid quadratic effects)
1565+ */
1566+ Py_ssize_t scale_factor = gcstate -> old [0 ].threshold ;
1567+ if (scale_factor < 2 ) {
1568+ scale_factor = 2 ;
1569+ }
1570+ Py_ssize_t new_objects = gcstate -> young .count ;
1571+ Py_ssize_t growth = gcstate -> heap_size - gcstate -> prior_heap_size ;
1572+ if (growth < 0 ) {
1573+ growth = 0 ;
1574+ }
1575+ if (gcstate -> heap_size < new_objects * scale_factor ) {
1576+ // Small heap: ignore growth
1577+ growth = 0 ;
1578+ }
1579+ Py_ssize_t heap_fraction = gcstate -> heap_size / SCAN_RATE_DIVISOR / scale_factor ;
1580+ if (heap_fraction > new_objects ) {
1581+ heap_fraction = new_objects ;
1582+ }
1583+ gcstate -> young .count = 0 ;
1584+ gcstate -> prior_heap_size = gcstate -> heap_size ;
1585+ return new_objects * 2 + growth * 2 + heap_fraction ;
1586+ }
1587+
15621588static void
15631589gc_collect_increment (PyThreadState * tstate , struct gc_collection_stats * stats )
15641590{
15651591 GC_STAT_ADD (1 , collections , 1 );
15661592 GCState * gcstate = & tstate -> interp -> gc ;
1567- gcstate -> work_to_do += gcstate -> young . count ;
1568- gcstate -> young . count = 0 ;
1593+
1594+ gcstate -> work_to_do += assess_work_to_do ( gcstate ) ;
15691595 if (gcstate -> phase == GC_PHASE_MARK ) {
15701596 Py_ssize_t objects_marked = mark_at_start (tstate );
15711597 GC_STAT_ADD (1 , objects_transitively_reachable , objects_marked );
@@ -1576,10 +1602,6 @@ gc_collect_increment(PyThreadState *tstate, struct gc_collection_stats *stats)
15761602 PyGC_Head * visited = & gcstate -> old [gcstate -> visited_space ].head ;
15771603 PyGC_Head increment ;
15781604 gc_list_init (& increment );
1579- Py_ssize_t scale_factor = gcstate -> old [0 ].threshold ;
1580- if (scale_factor < 1 ) {
1581- scale_factor = 1 ;
1582- }
15831605 Py_ssize_t objects_marked = mark_stacks (tstate -> interp , visited , gcstate -> visited_space , false);
15841606 GC_STAT_ADD (1 , objects_transitively_reachable , objects_marked );
15851607 gcstate -> work_to_do -= objects_marked ;
@@ -1607,7 +1629,6 @@ gc_collect_increment(PyThreadState *tstate, struct gc_collection_stats *stats)
16071629 gc_list_validate_space (& survivors , gcstate -> visited_space );
16081630 gc_list_merge (& survivors , visited );
16091631 assert (gc_list_is_empty (& increment ));
1610- gcstate -> work_to_do += gcstate -> heap_size / SCAN_RATE_DIVISOR / scale_factor ;
16111632 gcstate -> work_to_do -= increment_size ;
16121633
16131634 validate_old (gcstate );
0 commit comments