From bbad8e9d3929e13f90d2c6d5fe262e5fbbb814af Mon Sep 17 00:00:00 2001 From: Donghee Na Date: Sun, 4 Oct 2026 12:01:55 +0900 Subject: [PATCH 1/6] gh-158592: Skip reallocation when shrinking a small list --- .../2026-10-04-11-59-33.gh-issue-158592._BpoeY.rst | 2 ++ Objects/listobject.c | 13 ++++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) 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 81eb3e1102159b5..3e03c6c2db646e5 100644 --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -91,6 +91,8 @@ ensure_shared_on_resize(PyListObject *self) #endif } +#define LIST_SMALL_ALLOCATED 16 + /* 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 @@ -136,6 +138,15 @@ list_resize(PyListObject *self, Py_ssize_t newsize) if (newsize == 0) new_allocated = 0; + // gh-158602: when shrinking, do not reallocate the array of a small list. + if (newsize < allocated) { + if (allocated <= LIST_SMALL_ALLOCATED) { + Py_SET_SIZE(self, newsize); + return 0; + } + assert(new_allocated < (size_t)allocated); + } + ensure_shared_on_resize(self); #ifdef Py_GIL_DISABLED @@ -1148,7 +1159,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 23ad656bfc0cfee7fdef8462ade0aa0f0f5fb271 Mon Sep 17 00:00:00 2001 From: Donghee Na Date: Sun, 4 Oct 2026 21:29:06 +0900 Subject: [PATCH 2/6] Address code review --- Objects/listobject.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Objects/listobject.c b/Objects/listobject.c index 3e03c6c2db646e5..37e0bfe51fc6473 100644 --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -91,7 +91,7 @@ ensure_shared_on_resize(PyListObject *self) #endif } -#define LIST_SMALL_ALLOCATED 16 +#define LIST_SMALL_ALLOCATED 32 /* Ensure ob_item has room for at least newsize elements, and set * ob_size to newsize. If newsize > ob_size on entry, the content From 5cd80dbdf5e21346477606e4fca4cf51aea2f3a8 Mon Sep 17 00:00:00 2001 From: Donghee Na Date: Mon, 5 Oct 2026 15:15:33 +0900 Subject: [PATCH 3/6] Address code review --- Objects/listobject.c | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/Objects/listobject.c b/Objects/listobject.c index 37e0bfe51fc6473..3dca725c7936e81 100644 --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -111,8 +111,12 @@ list_resize(PyListObject *self, Py_ssize_t newsize) /* 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-158602: 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; @@ -138,14 +142,7 @@ list_resize(PyListObject *self, Py_ssize_t newsize) if (newsize == 0) new_allocated = 0; - // gh-158602: when shrinking, do not reallocate the array of a small list. - if (newsize < allocated) { - if (allocated <= LIST_SMALL_ALLOCATED) { - Py_SET_SIZE(self, newsize); - return 0; - } - assert(new_allocated < (size_t)allocated); - } + assert(newsize > allocated || new_allocated < (size_t)allocated); ensure_shared_on_resize(self); From 89b411f379adf2bd0f20d46c9e7ce82df7dee4f8 Mon Sep 17 00:00:00 2001 From: Donghee Na Date: Mon, 5 Oct 2026 15:35:45 +0900 Subject: [PATCH 4/6] Address code review --- Objects/listobject.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Objects/listobject.c b/Objects/listobject.c index 3dca725c7936e81..47c8c81d454f6a9 100644 --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -93,6 +93,8 @@ ensure_shared_on_resize(PyListObject *self) #define LIST_SMALL_ALLOCATED 32 +Py_NO_INLINE 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 @@ -102,10 +104,9 @@ ensure_shared_on_resize(PyListObject *self) * Note that self->ob_item may change, and even if newsize is less * than ob_size on entry. */ -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 @@ -121,6 +122,14 @@ list_resize(PyListObject *self, Py_ssize_t newsize) Py_SET_SIZE(self, newsize); return 0; } + return py_list_resize(self, newsize); +} + +Py_NO_INLINE 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 From a4f3cff91c872fd033ce013665750b91ab460c9f Mon Sep 17 00:00:00 2001 From: Donghee Na Date: Mon, 5 Oct 2026 16:03:03 +0900 Subject: [PATCH 5/6] Apply suggestion from @picnixz MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- .../2026-10-04-11-59-33.gh-issue-158592._BpoeY.rst | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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 index d937a81cc4630a0..8cc288198895c32 100644 --- 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 @@ -1,2 +1,4 @@ -Skip the array reallocation when shrinking a small :class:`list`. Patch by -Donghee Na. +Skip the array reallocation when shrinking a small :class:`list`- +When deleting an item from a list via :keyword:`del`, lists are +also now correctly reallocated unless small, thereby avoiding +holding memory for too long. Patch by Donghee Na. From dd855fdf74dbf62e60d2212a88b2d1f1a0717204 Mon Sep 17 00:00:00 2001 From: Donghee Na Date: Mon, 5 Oct 2026 16:13:52 +0900 Subject: [PATCH 6/6] Revert "Apply suggestion from @picnixz" This reverts commit 134f2fdb685e7439ad302a6bfb800345200d600e. --- .../2026-10-04-11-59-33.gh-issue-158592._BpoeY.rst | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) 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 index 8cc288198895c32..d937a81cc4630a0 100644 --- 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 @@ -1,4 +1,2 @@ -Skip the array reallocation when shrinking a small :class:`list`- -When deleting an item from a list via :keyword:`del`, lists are -also now correctly reallocated unless small, thereby avoiding -holding memory for too long. Patch by Donghee Na. +Skip the array reallocation when shrinking a small :class:`list`. Patch by +Donghee Na.