diff --git a/Include/internal/pycore_freelist_state.h b/Include/internal/pycore_freelist_state.h index 46e2a82ea03456..9f8a0f4de687eb 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 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 # 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/Lib/test/test_list.py b/Lib/test/test_list.py index 223f34fb696a6e..ad4037a7d552f5 100644 --- a/Lib/test/test_list.py +++ b/Lib/test/test_list.py @@ -332,8 +332,9 @@ def test_no_memory(self): import_module("_testcapi") code = textwrap.dedent(""" import _testcapi, 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 _testcapi.set_nomemory(0) l = [None] 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 00000000000000..2890b782cfdd48 --- /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. diff --git a/Objects/listobject.c b/Objects/listobject.c index 5905a6d335b311..f18109f80046b3 100644 --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -231,6 +231,21 @@ _PyList_DebugMallocStats(FILE *out) sizeof(PyListObject)); } + +static inline int +maybe_small_list_freelist_push(PyObject *self) +{ + assert(PyList_CheckExact(self)); + + PyListObject *op = (PyListObject *)self; + Py_ssize_t allocated = op->allocated; + if (allocated < PyList_MAXSAVESIZE) { + return _Py_FREELIST_PUSH(small_lists[allocated], self, Py_small_lists_MAXFREELIST); + } + return 0; +} + + PyObject * PyList_New(Py_ssize_t size) { @@ -239,35 +254,53 @@ PyList_New(Py_ssize_t size) return NULL; } - PyListObject *op = _Py_FREELIST_POP(PyListObject, lists); + PyListObject *op=0; + + 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 { + op->ob_item = NULL; + } + } + } if (op == NULL) { - op = PyObject_GC_New(PyListObject, &PyList_Type); + // the size based freelist was empty, so we try the general freelist + op = _Py_FREELIST_POP(PyListObject, lists); if (op == NULL) { - return NULL; + op = PyObject_GC_New(PyListObject, &PyList_Type); + if (op == NULL) { + return NULL; + } } - } - 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(); + if (size <= 0) { + op->ob_item = NULL; } - memset(&array->ob_item, 0, size * sizeof(PyObject *)); - op->ob_item = array->ob_item; + 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; } @@ -276,6 +309,14 @@ 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) { + assert (op->allocated >= size); + return (PyObject *) op; + } + } + PyListObject *op = (PyListObject *) PyList_New(0); if (op == NULL) { return NULL; @@ -545,6 +586,18 @@ PyList_Append(PyObject *op, PyObject *newitem) /* Methods */ +void _Py_small_list_freelist_free(void *obj) +{ + PyObject *self = (PyObject *)obj; + + assert(PyList_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) { @@ -560,11 +613,18 @@ list_dealloc(PyObject *self) while (--i >= 0) { Py_XDECREF(op->ob_item[i]); } + } + 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 0540112d7d2acf..129a93fb4ec2e2 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -917,6 +917,8 @@ free_object(void *obj) Py_DECREF(tp); } +extern void _Py_small_list_freelist_free(void *); + void _PyObject_ClearFreeLists(struct _Py_freelists *freelists, int is_finalization) { @@ -927,6 +929,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, _Py_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);