From 30888464355d44813957b78298697e09cfa1d543 Mon Sep 17 00:00:00 2001 From: Ilya Egorov <0x42005e1f@gmail.com> Date: Thu, 8 Oct 2026 03:56:38 +0400 Subject: [PATCH 1/2] [3.15] gh-158592: Reallocate the list's array when deleting by index (GH-158602) (cherry picked from commit 12fbc90e063b118f489241ae9f806a9d694a5682) --- .../2026-10-02-16-19-35.gh-issue-158592.V6W21J.rst | 1 + Objects/listobject.c | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-10-02-16-19-35.gh-issue-158592.V6W21J.rst diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-10-02-16-19-35.gh-issue-158592.V6W21J.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-10-02-16-19-35.gh-issue-158592.V6W21J.rst new file mode 100644 index 000000000000000..1112ca8e63d314d --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-10-02-16-19-35.gh-issue-158592.V6W21J.rst @@ -0,0 +1 @@ +``del list[index]`` now triggers shrinking the list's internal array. diff --git a/Objects/listobject.c b/Objects/listobject.c index 8a9c9bda68269b8..164eb3c12f972cd 100644 --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -1148,7 +1148,7 @@ list_ass_item_lock_held(PyListObject *a, Py_ssize_t i, PyObject *v) for (Py_ssize_t idx = i; idx < size - 1; idx++) { FT_ATOMIC_STORE_PTR_RELEASE(a->ob_item[idx], a->ob_item[idx + 1]); } - Py_SET_SIZE(a, size - 1); + list_resize(a, size - 1); // NB: shrinking a list can't fail } else { FT_ATOMIC_STORE_PTR_RELEASE(a->ob_item[i], Py_NewRef(v)); From ec19bda2b7545a308843e5b08e2a6aebad9ef185 Mon Sep 17 00:00:00 2001 From: Donghee Na Date: Thu, 8 Oct 2026 08:58:23 +0900 Subject: [PATCH 2/2] [3.15] gh-158592: Skip reallocation when shrinking a small list (GH-158787) (cherry picked from commit cdfdf7bb73a5751ecec7d2780e3f34ec60699fb1) --- ...-10-04-11-59-33.gh-issue-158592._BpoeY.rst | 2 ++ Objects/listobject.c | 27 ++++++++++++++++--- 2 files changed, 26 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-10-04-11-59-33.gh-issue-158592._BpoeY.rst diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-10-04-11-59-33.gh-issue-158592._BpoeY.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-10-04-11-59-33.gh-issue-158592._BpoeY.rst new file mode 100644 index 000000000000000..d937a81cc4630a0 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-10-04-11-59-33.gh-issue-158592._BpoeY.rst @@ -0,0 +1,2 @@ +Skip the array reallocation when shrinking a small :class:`list`. Patch by +Donghee Na. diff --git a/Objects/listobject.c b/Objects/listobject.c index 164eb3c12f972cd..c091b5889c40cbd 100644 --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -91,6 +91,10 @@ ensure_shared_on_resize(PyListObject *self) #endif } +#define LIST_SMALL_ALLOCATED 32 + +static int py_list_resize(PyListObject *self, Py_ssize_t newsize); + /* Ensure ob_item has room for at least newsize elements, and set * ob_size to newsize. If newsize > ob_size on entry, the content * of the new slots at exit is undefined heap trash; it's the caller's @@ -99,22 +103,37 @@ ensure_shared_on_resize(PyListObject *self) * Failure is impossible if newsize <= self.allocated on entry. * Note that self->ob_item may change, and even if newsize is less * than ob_size on entry. + * + * Always inlining list_resize() makes the fast path a few instructions + * in each caller instead of a function call. */ -static int +static inline Py_ALWAYS_INLINE int list_resize(PyListObject *self, Py_ssize_t newsize) { - size_t new_allocated, target_bytes; Py_ssize_t allocated = self->allocated; /* Bypass realloc() when a previous overallocation is large enough to accommodate the newsize. If the newsize falls lower than half the allocated size, then proceed with the realloc() to shrink the list. + gh-158592: do not shrink a small list, the realloc() cost is bigger + than the memory we get back. */ - if (allocated >= newsize && newsize >= (allocated >> 1)) { + if (allocated >= newsize + && (newsize >= (allocated >> 1) || allocated <= LIST_SMALL_ALLOCATED)) + { assert(self->ob_item != NULL || newsize == 0); Py_SET_SIZE(self, newsize); return 0; } + return py_list_resize(self, newsize); +} + +/* Slow path of list_resize(): allocate or reallocate ob_item. */ +static int +py_list_resize(PyListObject *self, Py_ssize_t newsize) +{ + size_t new_allocated, target_bytes; + Py_ssize_t allocated = self->allocated; /* This over-allocates proportional to the list size, making room * for additional growth. The over-allocation is mild, but is @@ -136,6 +155,8 @@ list_resize(PyListObject *self, Py_ssize_t newsize) if (newsize == 0) new_allocated = 0; + assert(newsize > allocated || new_allocated < (size_t)allocated); + ensure_shared_on_resize(self); #ifdef Py_GIL_DISABLED