[3.14] GH-139951: Fix major GC performance regression. Backport of GH-140262 (GH-140447)

* Count number of actually tracked objects, instead of trackable objects. This ensures that untracking tuples has the desired effect of reducing GC overhead
* Do not track most untrackable tuples during creation. This prevents large numbers of small tuples causing execessive GCs.
This commit is contained in:
Mark Shannon 2025-10-23 15:26:58 +01:00 committed by GitHub
parent 0fdae5f590
commit d1a434f7b2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 94 additions and 36 deletions

View file

@ -156,6 +156,18 @@ _PyTuple_MaybeUntrack(PyObject *op)
_PyObject_GC_UNTRACK(op);
}
/* Fast, but conservative check if an object maybe tracked
May return true for an object that is not tracked,
Will always return true for an object that is tracked.
This is a temporary workaround until _PyObject_GC_IS_TRACKED
becomes fast and safe to call on non-GC objects.
*/
static bool
maybe_tracked(PyObject *ob)
{
return _PyType_IS_GC(Py_TYPE(ob));
}
PyObject *
PyTuple_Pack(Py_ssize_t n, ...)
{
@ -163,6 +175,7 @@ PyTuple_Pack(Py_ssize_t n, ...)
PyObject *o;
PyObject **items;
va_list vargs;
bool track = false;
if (n == 0) {
return tuple_get_empty();
@ -177,10 +190,15 @@ PyTuple_Pack(Py_ssize_t n, ...)
items = result->ob_item;
for (i = 0; i < n; i++) {
o = va_arg(vargs, PyObject *);
if (!track && maybe_tracked(o)) {
track = true;
}
items[i] = Py_NewRef(o);
}
va_end(vargs);
_PyObject_GC_TRACK(result);
if (track) {
_PyObject_GC_TRACK(result);
}
return (PyObject *)result;
}
@ -377,11 +395,17 @@ _PyTuple_FromArray(PyObject *const *src, Py_ssize_t n)
return NULL;
}
PyObject **dst = tuple->ob_item;
bool track = false;
for (Py_ssize_t i = 0; i < n; i++) {
PyObject *item = src[i];
if (!track && maybe_tracked(item)) {
track = true;
}
dst[i] = Py_NewRef(item);
}
_PyObject_GC_TRACK(tuple);
if (track) {
_PyObject_GC_TRACK(tuple);
}
return (PyObject *)tuple;
}
@ -396,10 +420,17 @@ _PyTuple_FromStackRefStealOnSuccess(const _PyStackRef *src, Py_ssize_t n)
return NULL;
}
PyObject **dst = tuple->ob_item;
bool track = false;
for (Py_ssize_t i = 0; i < n; i++) {
dst[i] = PyStackRef_AsPyObjectSteal(src[i]);
PyObject *item = PyStackRef_AsPyObjectSteal(src[i]);
if (!track && maybe_tracked(item)) {
track = true;
}
dst[i] = item;
}
if (track) {
_PyObject_GC_TRACK(tuple);
}
_PyObject_GC_TRACK(tuple);
return (PyObject *)tuple;
}