Skip to content

Commit cdfdf7b

Browse files
authored
gh-158592: Skip reallocation when shrinking a small list (gh-158787)
1 parent 12fbc90 commit cdfdf7b

2 files changed

Lines changed: 26 additions & 3 deletions

File tree

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Skip the array reallocation when shrinking a small :class:`list`. Patch by
2+
Donghee Na.

‎Objects/listobject.c‎

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,10 @@ ensure_shared_on_resize(PyListObject *self)
9191
#endif
9292
}
9393

94+
#define LIST_SMALL_ALLOCATED 32
95+
96+
static int py_list_resize(PyListObject *self, Py_ssize_t newsize);
97+
9498
/* Ensure ob_item has room for at least newsize elements, and set
9599
* ob_size to newsize. If newsize > ob_size on entry, the content
96100
* 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)
99103
* Failure is impossible if newsize <= self.allocated on entry.
100104
* Note that self->ob_item may change, and even if newsize is less
101105
* than ob_size on entry.
106+
*
107+
* Always inlining list_resize() makes the fast path a few instructions
108+
* in each caller instead of a function call.
102109
*/
103-
static int
110+
static inline Py_ALWAYS_INLINE int
104111
list_resize(PyListObject *self, Py_ssize_t newsize)
105112
{
106-
size_t new_allocated, target_bytes;
107113
Py_ssize_t allocated = self->allocated;
108114

109115
/* Bypass realloc() when a previous overallocation is large enough
110116
to accommodate the newsize. If the newsize falls lower than half
111117
the allocated size, then proceed with the realloc() to shrink the list.
118+
gh-158592: do not shrink a small list, the realloc() cost is bigger
119+
than the memory we get back.
112120
*/
113-
if (allocated >= newsize && newsize >= (allocated >> 1)) {
121+
if (allocated >= newsize
122+
&& (newsize >= (allocated >> 1) || allocated <= LIST_SMALL_ALLOCATED))
123+
{
114124
assert(self->ob_item != NULL || newsize == 0);
115125
Py_SET_SIZE(self, newsize);
116126
return 0;
117127
}
128+
return py_list_resize(self, newsize);
129+
}
130+
131+
/* Slow path of list_resize(): allocate or reallocate ob_item. */
132+
static int
133+
py_list_resize(PyListObject *self, Py_ssize_t newsize)
134+
{
135+
size_t new_allocated, target_bytes;
136+
Py_ssize_t allocated = self->allocated;
118137

119138
/* This over-allocates proportional to the list size, making room
120139
* for additional growth. The over-allocation is mild, but is
@@ -136,6 +155,8 @@ list_resize(PyListObject *self, Py_ssize_t newsize)
136155
if (newsize == 0)
137156
new_allocated = 0;
138157

158+
assert(newsize > allocated || new_allocated < (size_t)allocated);
159+
139160
ensure_shared_on_resize(self);
140161

141162
#ifdef Py_GIL_DISABLED

0 commit comments

Comments
 (0)