From dbc372c417b55670bbce82c649e108b2a2c63e5c Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Fri, 7 Feb 2025 15:28:44 +0100 Subject: [PATCH 01/13] Create freelist for small lists --- Include/internal/pycore_freelist_state.h | 3 + Objects/listobject.c | 95 ++++++++++++++++++------ Objects/object.c | 5 ++ 3 files changed, 82 insertions(+), 21 deletions(-) diff --git a/Include/internal/pycore_freelist_state.h b/Include/internal/pycore_freelist_state.h index 46e2a82ea034560..b185a12ebf7cf71 100644 --- a/Include/internal/pycore_freelist_state.h +++ b/Include/internal/pycore_freelist_state.h @@ -10,6 +10,8 @@ extern "C" { # define PyTuple_MAXSAVESIZE 20 // Largest tuple to save on freelist # define Py_tuple_MAXFREELIST 2000 // Maximum number of tuples of each size to save +# define PyList_MAXSAVESIZE 10 // Largest list size to save on freelist (minus one) +# define Py_small_lists_MAXFREELIST 40 # define Py_lists_MAXFREELIST 80 # define Py_list_iters_MAXFREELIST 10 # define Py_tuple_iters_MAXFREELIST 10 @@ -48,6 +50,7 @@ struct _Py_freelists { struct _Py_freelist complexes; struct _Py_freelist ints; struct _Py_freelist tuples[PyTuple_MAXSAVESIZE]; + struct _Py_freelist small_lists[PyList_MAXSAVESIZE]; struct _Py_freelist lists; struct _Py_freelist list_iters; struct _Py_freelist tuple_iters; diff --git a/Objects/listobject.c b/Objects/listobject.c index f9488cc5c384bab..2402c18e78d2e8e 100644 --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -258,6 +258,27 @@ _PyList_DebugMallocStats(FILE *out) _PyType_PreHeaderSize(&PyList_Type) + sizeof(PyListObject)); } + +static inline int +maybe_small_list_freelist_push(PyObject *self) +{ + assert(PyList_CheckExact(self)); + assert(0); + + PyListObject *op = (PyListObject *)self; + PyObject **ob_item = _Py_atomic_load_ptr(&op->ob_item); + + + Py_ssize_t cap = op->allocated; // what is the difference between allocated and capacity in the FT build? + + printf("maybe_small_list_freelist_push: cap %d\n", cap); + if (cap < PyList_MAXSAVESIZE) { + return _Py_FREELIST_PUSH(small_lists[cap], self, Py_small_lists_MAXFREELIST); + } + return 0; +} + + PyObject * PyList_New(Py_ssize_t size) { @@ -266,35 +287,49 @@ PyList_New(Py_ssize_t size) return NULL; } - PyListObject *op = _Py_FREELIST_POP(PyListObject, lists); - if (op == NULL) { - op = PyObject_GC_New(PyListObject, &PyList_Type); - if (op == NULL) { - return NULL; - } + PyListObject *op; + + if (size < PyList_MAXSAVESIZE ) { + op = 0; + //op = (PyListObject *)_Py_FREELIST_POP(PyLongObject, small_lists[size]); } - if (size <= 0) { - op->ob_item = NULL; + if (op) { + // allocated with ob_item still allocated. we do need to set to zero? + if ( size>0) { + memset(&op->ob_item, 0, size * sizeof(PyObject *)); + } } else { -#ifdef Py_GIL_DISABLED - _PyListArray *array = list_allocate_array(size); - if (array == NULL) { - Py_DECREF(op); - return PyErr_NoMemory(); + op = _Py_FREELIST_POP(PyListObject, lists); + if (op == NULL) { + op = PyObject_GC_New(PyListObject, &PyList_Type); + if (op == NULL) { + return NULL; + } } - memset(&array->ob_item, 0, size * sizeof(PyObject *)); - op->ob_item = array->ob_item; + if (size <= 0) { + op->ob_item = NULL; + } + else { +#ifdef Py_GIL_DISABLED + _PyListArray *array = list_allocate_array(size); + if (array == NULL) { + Py_DECREF(op); + return PyErr_NoMemory(); + } + memset(&array->ob_item, 0, size * sizeof(PyObject *)); + op->ob_item = array->ob_item; #else - op->ob_item = (PyObject **) PyMem_Calloc(size, sizeof(PyObject *)); + op->ob_item = (PyObject **) PyMem_Calloc(size, sizeof(PyObject *)); + if (op->ob_item == NULL) { + Py_DECREF(op); + return PyErr_NoMemory(); + } #endif - if (op->ob_item == NULL) { - Py_DECREF(op); - return PyErr_NoMemory(); } + Py_SET_SIZE(op, size); + op->allocated = size; } - Py_SET_SIZE(op, size); - op->allocated = size; _PyObject_GC_TRACK(op); return (PyObject *) op; } @@ -606,6 +641,18 @@ PyList_Append(PyObject *op, PyObject *newitem) /* Methods */ +void small_list_freelist_free(PyObject *self) +{ + assert(0); + printf("small_list_freelist_free\n"); + assert(PyLong_CheckExact(self)); + PyListObject *op = (PyListObject *)self; + if (op->ob_item != NULL) { + free_list_items(op->ob_item, false); + } + PyObject_GC_Del(op); +} + static void list_dealloc(PyObject *self) { @@ -621,6 +668,12 @@ list_dealloc(PyObject *self) while (--i >= 0) { Py_XDECREF(op->ob_item[i]); } + } + if (0 && maybe_small_list_freelist_push(self) ) { + return; + } + + if (op->ob_item != NULL) { free_list_items(op->ob_item, false); op->ob_item = NULL; } diff --git a/Objects/object.c b/Objects/object.c index f8206e5d3eca329..505025577ebc7b7 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -923,6 +923,8 @@ free_object(void *obj) Py_DECREF(tp); } +extern void small_list_freelist_free(PyObject *); + void _PyObject_ClearFreeLists(struct _Py_freelists *freelists, int is_finalization) { @@ -933,6 +935,9 @@ _PyObject_ClearFreeLists(struct _Py_freelists *freelists, int is_finalization) for (Py_ssize_t i = 0; i < PyTuple_MAXSAVESIZE; i++) { clear_freelist(&freelists->tuples[i], is_finalization, free_object); } + for (Py_ssize_t i = 0; i < PyList_MAXSAVESIZE; i++) { + clear_freelist(&freelists->small_lists[i], is_finalization, small_list_freelist_free); + } clear_freelist(&freelists->lists, is_finalization, free_object); clear_freelist(&freelists->list_iters, is_finalization, free_object); clear_freelist(&freelists->tuple_iters, is_finalization, free_object); From 0777dbae620d3a08d9e3604fd278cf6c2ac33f20 Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Fri, 7 Feb 2025 21:19:51 +0100 Subject: [PATCH 02/13] working version --- Include/internal/pycore_freelist_state.h | 2 +- Objects/listobject.c | 53 +++++++++++++----------- Objects/object.c | 2 +- 3 files changed, 30 insertions(+), 27 deletions(-) diff --git a/Include/internal/pycore_freelist_state.h b/Include/internal/pycore_freelist_state.h index b185a12ebf7cf71..9f8a0f4de687eb5 100644 --- a/Include/internal/pycore_freelist_state.h +++ b/Include/internal/pycore_freelist_state.h @@ -10,7 +10,7 @@ extern "C" { # define PyTuple_MAXSAVESIZE 20 // Largest tuple to save on freelist # define Py_tuple_MAXFREELIST 2000 // Maximum number of tuples of each size to save -# define PyList_MAXSAVESIZE 10 // Largest list size to save on freelist (minus one) +# define PyList_MAXSAVESIZE 13 // Largest list size to save on freelist (minus one) # define Py_small_lists_MAXFREELIST 40 # define Py_lists_MAXFREELIST 80 # define Py_list_iters_MAXFREELIST 10 diff --git a/Objects/listobject.c b/Objects/listobject.c index 2402c18e78d2e8e..d839b1aea3e7c6c 100644 --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -263,17 +263,14 @@ static inline int maybe_small_list_freelist_push(PyObject *self) { assert(PyList_CheckExact(self)); - assert(0); PyListObject *op = (PyListObject *)self; PyObject **ob_item = _Py_atomic_load_ptr(&op->ob_item); - - Py_ssize_t cap = op->allocated; // what is the difference between allocated and capacity in the FT build? - - printf("maybe_small_list_freelist_push: cap %d\n", cap); - if (cap < PyList_MAXSAVESIZE) { - return _Py_FREELIST_PUSH(small_lists[cap], self, Py_small_lists_MAXFREELIST); + Py_ssize_t allocated = op->allocated; + if (allocated < PyList_MAXSAVESIZE) { + return _Py_FREELIST_PUSH(small_lists[allocated], self, Py_small_lists_MAXFREELIST); + //printf("maybe_small_list_freelist_push: allocated %d (mod %d), size %d, id %p\n", (int)allocated, op->allocated, (int)PyList_Size(self), (void *)self); } return 0; } @@ -287,19 +284,23 @@ PyList_New(Py_ssize_t size) return NULL; } - PyListObject *op; + PyListObject *op=0; - if (size < PyList_MAXSAVESIZE ) { - op = 0; - //op = (PyListObject *)_Py_FREELIST_POP(PyLongObject, small_lists[size]); - } - if (op) { - // allocated with ob_item still allocated. we do need to set to zero? - if ( size>0) { - memset(&op->ob_item, 0, size * sizeof(PyObject *)); + if (size < PyList_MAXSAVESIZE) { + op = (PyListObject *)_Py_FREELIST_POP(PyLongObject, small_lists[size]); + if (op) { + // allocated with ob_item still allocated, but we need to set the other fields + Py_SET_SIZE(op, size); + if ( size>0) { + memset(op->ob_item, 0, size * sizeof(PyObject *)); + } + //printf("obtained list from small_lists[%d] (id %p, op->allocated %d, size %d, PyList_Size %d)\n", (int) size, op, (int)op->allocated, (int)size, (int)PyList_Size( (PyObject*)op)); + assert (op->allocated >= size); } + op=0; } - else { + if (!op) { + // do we still need this freelist? if so, we could store it at small_lists[PyList_MAXSAVESIZE-1] with some special casing op = _Py_FREELIST_POP(PyListObject, lists); if (op == NULL) { op = PyObject_GC_New(PyListObject, &PyList_Type); @@ -641,12 +642,13 @@ PyList_Append(PyObject *op, PyObject *newitem) /* Methods */ -void small_list_freelist_free(PyObject *self) +void small_list_freelist_free(void *obj) { - assert(0); - printf("small_list_freelist_free\n"); - assert(PyLong_CheckExact(self)); + PyObject *self = (PyObject *)obj; + + assert(PyList_CheckExact(self)); PyListObject *op = (PyListObject *)self; + //printf("small_list_freelist_free: (op->allocated %d, size %d)\n", (int)op->allocated, (int)PyList_Size(self)); if (op->ob_item != NULL) { free_list_items(op->ob_item, false); } @@ -669,16 +671,17 @@ list_dealloc(PyObject *self) Py_XDECREF(op->ob_item[i]); } } - if (0 && maybe_small_list_freelist_push(self) ) { - return; + if (PyList_CheckExact(op)) { + if( maybe_small_list_freelist_push(self) ) { + return; + } } - if (op->ob_item != NULL) { free_list_items(op->ob_item, false); op->ob_item = NULL; } if (PyList_CheckExact(op)) { - _Py_FREELIST_FREE(lists, op, PyObject_GC_Del); + _Py_FREELIST_FREE(lists, op, PyObject_GC_Del); } else { PyObject_GC_Del(op); diff --git a/Objects/object.c b/Objects/object.c index 505025577ebc7b7..e74f3332cfcf4c6 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -923,7 +923,7 @@ free_object(void *obj) Py_DECREF(tp); } -extern void small_list_freelist_free(PyObject *); +extern void small_list_freelist_free(void *); void _PyObject_ClearFreeLists(struct _Py_freelists *freelists, int is_finalization) From 814815a39eaff64f78705e2db37cf09b0f5ef3fe Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Sun, 9 Feb 2025 19:52:27 +0100 Subject: [PATCH 03/13] comments --- Objects/listobject.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Objects/listobject.c b/Objects/listobject.c index d839b1aea3e7c6c..04916b1f8e1f59d 100644 --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -262,11 +262,10 @@ _PyList_DebugMallocStats(FILE *out) static inline int maybe_small_list_freelist_push(PyObject *self) { + // todo: in the resizing there are some alignments on the allocation. can we use that here? assert(PyList_CheckExact(self)); PyListObject *op = (PyListObject *)self; - PyObject **ob_item = _Py_atomic_load_ptr(&op->ob_item); - Py_ssize_t allocated = op->allocated; if (allocated < PyList_MAXSAVESIZE) { return _Py_FREELIST_PUSH(small_lists[allocated], self, Py_small_lists_MAXFREELIST); @@ -297,9 +296,9 @@ PyList_New(Py_ssize_t size) //printf("obtained list from small_lists[%d] (id %p, op->allocated %d, size %d, PyList_Size %d)\n", (int) size, op, (int)op->allocated, (int)size, (int)PyList_Size( (PyObject*)op)); assert (op->allocated >= size); } - op=0; + //op=0; } - if (!op) { + if (op == NULL) { // do we still need this freelist? if so, we could store it at small_lists[PyList_MAXSAVESIZE-1] with some special casing op = _Py_FREELIST_POP(PyListObject, lists); if (op == NULL) { From 6e5c67301307cf90287bcb4de2d035118c4b5096 Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Sun, 9 Feb 2025 22:45:51 +0100 Subject: [PATCH 04/13] clear debugging code --- Objects/listobject.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Objects/listobject.c b/Objects/listobject.c index 04916b1f8e1f59d..a2f945f21277f3e 100644 --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -269,7 +269,6 @@ maybe_small_list_freelist_push(PyObject *self) Py_ssize_t allocated = op->allocated; if (allocated < PyList_MAXSAVESIZE) { return _Py_FREELIST_PUSH(small_lists[allocated], self, Py_small_lists_MAXFREELIST); - //printf("maybe_small_list_freelist_push: allocated %d (mod %d), size %d, id %p\n", (int)allocated, op->allocated, (int)PyList_Size(self), (void *)self); } return 0; } @@ -293,13 +292,12 @@ PyList_New(Py_ssize_t size) if ( size>0) { memset(op->ob_item, 0, size * sizeof(PyObject *)); } - //printf("obtained list from small_lists[%d] (id %p, op->allocated %d, size %d, PyList_Size %d)\n", (int) size, op, (int)op->allocated, (int)size, (int)PyList_Size( (PyObject*)op)); assert (op->allocated >= size); } //op=0; } if (op == NULL) { - // do we still need this freelist? if so, we could store it at small_lists[PyList_MAXSAVESIZE-1] with some special casing + // do we still need this freelist? if so, we could store it at small_lists[0] with some special casing op = _Py_FREELIST_POP(PyListObject, lists); if (op == NULL) { op = PyObject_GC_New(PyListObject, &PyList_Type); @@ -338,6 +336,15 @@ static PyObject * list_new_prealloc(Py_ssize_t size) { assert(size > 0); + if (size < PyList_MAXSAVESIZE) { + PyListObject *op = (PyListObject *)_Py_FREELIST_POP(PyLongObject, small_lists[size]); + if (op) { + // allocated with ob_item still allocated, but we need to set the other fields + assert (op->allocated >= size); + return op; + } + } + PyListObject *op = (PyListObject *) PyList_New(0); if (op == NULL) { return NULL; @@ -647,7 +654,6 @@ void small_list_freelist_free(void *obj) assert(PyList_CheckExact(self)); PyListObject *op = (PyListObject *)self; - //printf("small_list_freelist_free: (op->allocated %d, size %d)\n", (int)op->allocated, (int)PyList_Size(self)); if (op->ob_item != NULL) { free_list_items(op->ob_item, false); } From 258c72aad5e0e18896c855eca2a46a2575e0c715 Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Mon, 10 Feb 2025 14:33:31 +0100 Subject: [PATCH 05/13] ci --- Objects/listobject.c | 7 +++++-- Objects/object.c | 4 ++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Objects/listobject.c b/Objects/listobject.c index a2f945f21277f3e..05ab7241df1468f 100644 --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -291,6 +291,9 @@ PyList_New(Py_ssize_t size) Py_SET_SIZE(op, size); if ( size>0) { memset(op->ob_item, 0, size * sizeof(PyObject *)); + } else { + // might be relatex later + op->ob_item = NULL; } assert (op->allocated >= size); } @@ -341,7 +344,7 @@ list_new_prealloc(Py_ssize_t size) if (op) { // allocated with ob_item still allocated, but we need to set the other fields assert (op->allocated >= size); - return op; + return (PyObject *) op; } } @@ -648,7 +651,7 @@ PyList_Append(PyObject *op, PyObject *newitem) /* Methods */ -void small_list_freelist_free(void *obj) +void _Py_small_list_freelist_free(void *obj) { PyObject *self = (PyObject *)obj; diff --git a/Objects/object.c b/Objects/object.c index e74f3332cfcf4c6..1bfe44b9324bda6 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -923,7 +923,7 @@ free_object(void *obj) Py_DECREF(tp); } -extern void small_list_freelist_free(void *); +extern void _Py_small_list_freelist_free(void *); void _PyObject_ClearFreeLists(struct _Py_freelists *freelists, int is_finalization) @@ -936,7 +936,7 @@ _PyObject_ClearFreeLists(struct _Py_freelists *freelists, int is_finalization) clear_freelist(&freelists->tuples[i], is_finalization, free_object); } for (Py_ssize_t i = 0; i < PyList_MAXSAVESIZE; i++) { - clear_freelist(&freelists->small_lists[i], is_finalization, small_list_freelist_free); + clear_freelist(&freelists->small_lists[i], is_finalization, _Py_small_list_freelist_free); } clear_freelist(&freelists->lists, is_finalization, free_object); clear_freelist(&freelists->list_iters, is_finalization, free_object); From ef4052ae502ac88af90a3b952f3e7eb7bfd40b49 Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Sun, 30 Mar 2025 20:56:16 +0200 Subject: [PATCH 06/13] fix test --- Lib/test/test_list.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_list.py b/Lib/test/test_list.py index 260f9f4f607f152..9341727b970ef42 100644 --- a/Lib/test/test_list.py +++ b/Lib/test/test_list.py @@ -379,8 +379,9 @@ def test_no_memory(self): code = textwrap.dedent(""" from test import support import sys - # Prime the freelist - l = [None] + # Prime the freelist, size needs to larger than the small list freelists + l = [None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None,] del l support.inject_memory_error() l = [None] From fb6b553fbcb36157bb751744e7debd84fba16a0a Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Thu, 3 Apr 2025 23:15:49 +0200 Subject: [PATCH 07/13] increase size used to that of the tuples --- Include/internal/pycore_freelist_state.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Include/internal/pycore_freelist_state.h b/Include/internal/pycore_freelist_state.h index 9f8a0f4de687eb5..ce1d4da96fbc529 100644 --- a/Include/internal/pycore_freelist_state.h +++ b/Include/internal/pycore_freelist_state.h @@ -10,7 +10,7 @@ extern "C" { # define PyTuple_MAXSAVESIZE 20 // Largest tuple to save on freelist # define Py_tuple_MAXFREELIST 2000 // Maximum number of tuples of each size to save -# define PyList_MAXSAVESIZE 13 // Largest list size to save on freelist (minus one) +# define PyList_MAXSAVESIZE 21 // Largest list size to save on freelist (minus one) # define Py_small_lists_MAXFREELIST 40 # define Py_lists_MAXFREELIST 80 # define Py_list_iters_MAXFREELIST 10 From be95b3d725ae1a46881e05830ba77cbd72d151f4 Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Thu, 3 Apr 2025 23:21:21 +0200 Subject: [PATCH 08/13] revert size increase --- Include/internal/pycore_freelist_state.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Include/internal/pycore_freelist_state.h b/Include/internal/pycore_freelist_state.h index ce1d4da96fbc529..9f8a0f4de687eb5 100644 --- a/Include/internal/pycore_freelist_state.h +++ b/Include/internal/pycore_freelist_state.h @@ -10,7 +10,7 @@ extern "C" { # define PyTuple_MAXSAVESIZE 20 // Largest tuple to save on freelist # define Py_tuple_MAXFREELIST 2000 // Maximum number of tuples of each size to save -# define PyList_MAXSAVESIZE 21 // Largest list size to save on freelist (minus one) +# define PyList_MAXSAVESIZE 13 // Largest list size to save on freelist (minus one) # define Py_small_lists_MAXFREELIST 40 # define Py_lists_MAXFREELIST 80 # define Py_list_iters_MAXFREELIST 10 From 76799fcc172d8a0333fc9a554dadd7ae799a477e Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Thu, 3 Apr 2025 21:23:08 +0000 Subject: [PATCH 09/13] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20b?= =?UTF-8?q?lurb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2025-04-03-21-23-05.gh-issue-126703.6JpmrR.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2025-04-03-21-23-05.gh-issue-126703.6JpmrR.rst diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-03-21-23-05.gh-issue-126703.6JpmrR.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-04-03-21-23-05.gh-issue-126703.6JpmrR.rst new file mode 100644 index 000000000000000..2890b782cfdd488 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-04-03-21-23-05.gh-issue-126703.6JpmrR.rst @@ -0,0 +1 @@ +Improve performance of :class:`list` by using dedicated freelists for small sizes. From ce8c2e049e1641ce745baf9c905fbe0be40e64c1 Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Sun, 6 Apr 2025 22:38:45 +0200 Subject: [PATCH 10/13] cleanup --- Objects/listobject.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Objects/listobject.c b/Objects/listobject.c index 05ab7241df1468f..704fb1df781f338 100644 --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -262,7 +262,6 @@ _PyList_DebugMallocStats(FILE *out) static inline int maybe_small_list_freelist_push(PyObject *self) { - // todo: in the resizing there are some alignments on the allocation. can we use that here? assert(PyList_CheckExact(self)); PyListObject *op = (PyListObject *)self; @@ -287,17 +286,15 @@ PyList_New(Py_ssize_t size) if (size < PyList_MAXSAVESIZE) { op = (PyListObject *)_Py_FREELIST_POP(PyLongObject, small_lists[size]); if (op) { + assert (op->allocated >= size); // allocated with ob_item still allocated, but we need to set the other fields Py_SET_SIZE(op, size); if ( size>0) { memset(op->ob_item, 0, size * sizeof(PyObject *)); } else { - // might be relatex later op->ob_item = NULL; } - assert (op->allocated >= size); } - //op=0; } if (op == NULL) { // do we still need this freelist? if so, we could store it at small_lists[0] with some special casing From 6bf65c7884e5c410d06f596f114c457bac49f0d3 Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Tue, 8 Apr 2025 22:47:57 +0200 Subject: [PATCH 11/13] update comment --- Objects/listobject.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Objects/listobject.c b/Objects/listobject.c index 704fb1df781f338..48157a5d013c555 100644 --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -297,7 +297,7 @@ PyList_New(Py_ssize_t size) } } if (op == NULL) { - // do we still need this freelist? if so, we could store it at small_lists[0] with some special casing + // the size based freelist was empty, so we try the general freelist op = _Py_FREELIST_POP(PyListObject, lists); if (op == NULL) { op = PyObject_GC_New(PyListObject, &PyList_Type); From 69dd56b38215a73bf7f085726e3ab9baa51439a5 Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Tue, 8 Apr 2025 22:49:01 +0200 Subject: [PATCH 12/13] update comment --- Objects/listobject.c | 1 - 1 file changed, 1 deletion(-) diff --git a/Objects/listobject.c b/Objects/listobject.c index 48157a5d013c555..4d5a2c6e01e16e6 100644 --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -339,7 +339,6 @@ list_new_prealloc(Py_ssize_t size) if (size < PyList_MAXSAVESIZE) { PyListObject *op = (PyListObject *)_Py_FREELIST_POP(PyLongObject, small_lists[size]); if (op) { - // allocated with ob_item still allocated, but we need to set the other fields assert (op->allocated >= size); return (PyObject *) op; } From eb0a0d832cb7752ab4b99a5b299472d37329dd73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Langa?= Date: Wed, 21 Oct 2026 03:27:43 +0200 Subject: [PATCH 13/13] Fix CI: force fresh list buffer alloc in test_no_memory (gh-126703) Small-list freelist entries keep their ob_item buffers, so a plain [None] after inject_memory_error() may succeed instead of failing. Repeat the list to a size the freelists cannot satisfy, forcing a real allocation. Co-authored-by: Muse Code (Meta Muse Spark) --- Lib/test/test_list.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_list.py b/Lib/test/test_list.py index 9341727b970ef42..070e4590f57df9a 100644 --- a/Lib/test/test_list.py +++ b/Lib/test/test_list.py @@ -384,7 +384,9 @@ def test_no_memory(self): None, None, None, None, None, None, None, None, None,] del l support.inject_memory_error() - l = [None] + # Repeat to force a fresh ob_item allocation: entries on the small + # list freelists keep their buffers, so a plain [None] may succeed. + l = [None] * 30 """) rc, _, _ = assert_python_failure("-c", code) if support.MS_WINDOWS: