@@ -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
@@ -103,22 +107,37 @@ ensure_shared_on_resize(PyListObject *self)
103107 * imagine a realloc implementation where it wouldn't be true).
104108 * Note that self->ob_item may change, and even if newsize is less
105109 * than ob_size on entry.
110+ *
111+ * Always inlining list_resize() makes the fast path a few instructions
112+ * in each caller instead of a function call.
106113 */
107- static int
114+ static inline Py_ALWAYS_INLINE int
108115list_resize (PyListObject * self , Py_ssize_t newsize )
109116{
110- size_t new_allocated , target_bytes ;
111117 Py_ssize_t allocated = self -> allocated ;
112118
113119 /* Bypass realloc() when a previous overallocation is large enough
114120 to accommodate the newsize. If the newsize falls lower than half
115121 the allocated size, then proceed with the realloc() to shrink the list.
122+ gh-158592: do not shrink a small list, the realloc() cost is bigger
123+ than the memory we get back.
116124 */
117- if (allocated >= newsize && newsize >= (allocated >> 1 )) {
125+ if (allocated >= newsize
126+ && (newsize >= (allocated >> 1 ) || allocated <= LIST_SMALL_ALLOCATED ))
127+ {
118128 assert (self -> ob_item != NULL || newsize == 0 );
119129 Py_SET_SIZE (self , newsize );
120130 return 0 ;
121131 }
132+ return py_list_resize (self , newsize );
133+ }
134+
135+ /* Slow path of list_resize(): allocate or reallocate ob_item. */
136+ static int
137+ py_list_resize (PyListObject * self , Py_ssize_t newsize )
138+ {
139+ size_t new_allocated , target_bytes ;
140+ Py_ssize_t allocated = self -> allocated ;
122141
123142 /* This over-allocates proportional to the list size, making room
124143 * for additional growth. The over-allocation is mild, but is
@@ -140,6 +159,8 @@ list_resize(PyListObject *self, Py_ssize_t newsize)
140159 if (newsize == 0 )
141160 new_allocated = 0 ;
142161
162+ assert (newsize > allocated || new_allocated < (size_t )allocated );
163+
143164 ensure_shared_on_resize (self );
144165
145166#ifdef Py_GIL_DISABLED
@@ -1093,7 +1114,7 @@ list_ass_item_lock_held(PyListObject *a, Py_ssize_t i, PyObject *v)
10931114 for (Py_ssize_t idx = i ; idx < size - 1 ; idx ++ ) {
10941115 FT_ATOMIC_STORE_PTR_RELAXED (a -> ob_item [idx ], a -> ob_item [idx + 1 ]);
10951116 }
1096- Py_SET_SIZE (a , size - 1 );
1117+ list_resize (a , size - 1 ); // NB: shrinking a list can't fail
10971118 }
10981119 else {
10991120 FT_ATOMIC_STORE_PTR_RELEASE (a -> ob_item [i ], Py_NewRef (v ));
0 commit comments