Skip to content

Commit 40657c7

Browse files
committed
gh-142531: Fix free-threaded GC performance regression
If there are many untracked tuples, the GC will run too often, resulting in poor performance. The fix is to include untracked tuples in the "long lived" object count. The number of frozen objects is also now included since the free-threaded GC must scan those too.
1 parent bc9e63d commit 40657c7

File tree

2 files changed

+28
-3
lines changed

2 files changed

+28
-3
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Fix a free-threaded GC performance regression. If there are many untracked
2+
tuples, the GC will run too often, resulting in poor performance. The fix
3+
is to include untracked tuples in the "long lived" object count. The number
4+
of frozen objects is also now included since the free-threaded GC must
5+
scan those too.

Python/gc_free_threading.c

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,19 @@ op_from_block(void *block, void *arg, bool include_frozen)
375375
return op;
376376
}
377377

378+
// As above but returns untracked and frozen objects as well.
379+
static PyObject *
380+
op_from_block_all_gc(void *block, void *arg)
381+
{
382+
struct visitor_args *a = arg;
383+
if (block == NULL) {
384+
return NULL;
385+
}
386+
PyObject *op = (PyObject *)((char*)block + a->offset);
387+
assert(PyObject_IS_GC(op));
388+
return op;
389+
}
390+
378391
static int
379392
gc_visit_heaps_lock_held(PyInterpreterState *interp, mi_block_visit_fun *visitor,
380393
struct visitor_args *arg)
@@ -1186,12 +1199,20 @@ static bool
11861199
scan_heap_visitor(const mi_heap_t *heap, const mi_heap_area_t *area,
11871200
void *block, size_t block_size, void *args)
11881201
{
1189-
PyObject *op = op_from_block(block, args, false);
1202+
PyObject *op = op_from_block_all_gc(block, args);
11901203
if (op == NULL) {
11911204
return true;
11921205
}
1193-
11941206
struct collection_state *state = (struct collection_state *)args;
1207+
// The free-threaded GC cost is proportional to the number of objects in
1208+
// the mimalloc GC heap and so we should include the counts for untracked
1209+
// and frozen objects as well. This is especially important if many
1210+
// tuples have been untracked.
1211+
state->long_lived_total++;
1212+
if (!_PyObject_GC_IS_TRACKED(op) || gc_is_frozen(op)) {
1213+
return true;
1214+
}
1215+
11951216
if (gc_is_unreachable(op)) {
11961217
// Disable deferred refcounting for unreachable objects so that they
11971218
// are collected immediately after finalization.
@@ -1222,7 +1243,6 @@ scan_heap_visitor(const mi_heap_t *heap, const mi_heap_area_t *area,
12221243
// object is reachable, restore `ob_tid`; we're done with these objects
12231244
gc_restore_tid(op);
12241245
gc_clear_alive(op);
1225-
state->long_lived_total++;
12261246
return true;
12271247
}
12281248

0 commit comments

Comments
 (0)