callback.assert_not_called()
gc.enable()
+
+class IncrementalGCTests(unittest.TestCase):
+
+ def setUp(self):
+ # Reenable GC as it is disabled module-wide
+ gc.enable()
+
+ def tearDown(self):
+ gc.disable()
+
@unittest.skipIf(Py_GIL_DISABLED, "Free threading does not support incremental GC")
+ # Use small increments to emulate longer running process in a shorter time
+ @gc_threshold(200, 10)
def test_incremental_gc_handles_fast_cycle_creation(self):
class LinkedList:
head = LinkedList(head, head.prev)
return head
- head = make_ll(10000)
- count = 10000
+ head = make_ll(1000)
+ count = 1000
- # We expect the counts to go negative eventually
- # as there will some objects we aren't counting,
- # e.g. the gc stats dicts. The test merely checks
- # that the counts don't grow.
+ # There will be some objects we aren't counting,
+ # e.g. the gc stats dicts. This test checks
+ # that the counts don't grow, so we try to
+ # correct for the uncounted objects
+ # This is just an estimate.
+ CORRECTION = 20
enabled = gc.isenabled()
gc.enable()
olds = []
- for i in range(1000):
- newhead = make_ll(200)
- count += 200
+ for i in range(20_000):
+ newhead = make_ll(20)
+ count += 20
newhead.surprise = head
olds.append(newhead)
- if len(olds) == 50:
+ if len(olds) == 20:
stats = gc.get_stats()
young = stats[0]
incremental = stats[1]
old = stats[2]
collected = young['collected'] + incremental['collected'] + old['collected']
+ count += CORRECTION
live = count - collected
self.assertLess(live, 25000)
del olds[:]
if (gcstate->callbacks == NULL) {
return _PyStatus_NO_MEMORY();
}
+ gcstate->heap_size = 0;
return _PyStatus_OK();
}
struct gc_collection_stats *stats);
static inline Py_ssize_t
-gc_list_set_space(PyGC_Head *list, uintptr_t space)
+gc_list_set_space(PyGC_Head *list, int space)
{
Py_ssize_t size = 0;
PyGC_Head *gc;
* N == 1.4 (1 + 4/threshold)
*/
-/* Multiply by 4 so that the default incremental threshold of 10
- * scans objects at 20% the rate of object creation */
-#define SCAN_RATE_MULTIPLIER 2
+/* Divide by 10, so that the default incremental threshold of 10
+ * scans objects at 1% of the heap size */
+#define SCAN_RATE_DIVISOR 10
static void
add_stats(GCState *gcstate, int gen, struct gc_collection_stats *stats)
if (scale_factor < 1) {
scale_factor = 1;
}
- gcstate->work_to_do += survivor_count + survivor_count * SCAN_RATE_MULTIPLIER / scale_factor;
+ gcstate->work_to_do += gcstate->heap_size / SCAN_RATE_DIVISOR / scale_factor;
add_stats(gcstate, 0, stats);
}
static void
completed_cycle(GCState *gcstate)
{
+#ifdef Py_DEBUG
PyGC_Head *not_visited = &gcstate->old[gcstate->visited_space^1].head;
assert(gc_list_is_empty(not_visited));
+#endif
gcstate->visited_space = flip_old_space(gcstate->visited_space);
- if (gcstate->work_to_do > 0) {
- gcstate->work_to_do = 0;
- }
+ gcstate->work_to_do = 0;
}
static void
if (scale_factor < 1) {
scale_factor = 1;
}
- Py_ssize_t increment_size = 0;
gc_list_merge(&gcstate->young.head, &increment);
gcstate->young.count = 0;
if (gcstate->visited_space) {
/* objects in visited space have bit set, so we set it here */
gc_list_set_space(&increment, 1);
}
+ Py_ssize_t increment_size = 0;
while (increment_size < gcstate->work_to_do) {
if (gc_list_is_empty(not_visited)) {
break;
PyGC_Head survivors;
gc_list_init(&survivors);
gc_collect_region(tstate, &increment, &survivors, UNTRACK_TUPLES, stats);
- Py_ssize_t survivor_count = gc_list_size(&survivors);
gc_list_merge(&survivors, visited);
assert(gc_list_is_empty(&increment));
- gcstate->work_to_do += survivor_count + survivor_count * SCAN_RATE_MULTIPLIER / scale_factor;
+ gcstate->work_to_do += gcstate->heap_size / SCAN_RATE_DIVISOR / scale_factor;
gcstate->work_to_do -= increment_size;
- if (gcstate->work_to_do < 0) {
- gcstate->work_to_do = 0;
- }
+
validate_old(gcstate);
add_stats(gcstate, 1, stats);
if (gc_list_is_empty(not_visited)) {
}
PyObject *
-_PyGC_GetObjects(PyInterpreterState *interp, Py_ssize_t generation)
+_PyGC_GetObjects(PyInterpreterState *interp, int generation)
{
assert(generation >= -1 && generation < NUM_GENERATIONS);
GCState *gcstate = &interp->gc;
gc->_gc_next = 0;
gc->_gc_prev = 0;
gcstate->young.count++; /* number of allocated GC objects */
+ gcstate->heap_size++;
if (gcstate->young.count > gcstate->young.threshold &&
gcstate->enabled &&
gcstate->young.threshold &&
if (gcstate->young.count > 0) {
gcstate->young.count--;
}
+ gcstate->heap_size--;
PyObject_Free(((char *)op)-presize);
}