From 9942f42a93ccda047fd3558c47b822e99afe10c0 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Thu, 21 Oct 2021 16:12:20 +0300 Subject: [PATCH] bpo-45522: Allow to disable freelists on build time (GH-29056) Freelists for object structs can now be disabled. A new ``configure`` option ``--without-freelists`` can be used to disable all freelists except empty tuple singleton. Internal Py*_MAXFREELIST macros can now be defined as 0 without causing compiler warnings and segfaults. Signed-off-by: Christian Heimes --- Doc/whatsnew/3.11.rst | 5 +++ Include/internal/pycore_interp.h | 37 +++++++++++++++++++ Lib/test/test_sys.py | 13 ++++++- .../2021-10-19-13-07-46.bpo-45522.kGAwmZ.rst | 2 + Objects/dictobject.c | 27 ++++++++++++-- Objects/floatobject.c | 21 +++++++++-- Objects/frameobject.c | 23 +++++++++--- Objects/genobject.c | 26 ++++++++++--- Objects/listobject.c | 24 +++++++++--- Objects/tupleobject.c | 3 +- PC/pyconfig.h | 3 ++ Python/context.c | 23 ++++++++---- configure | 26 +++++++++++++ configure.ac | 17 +++++++++ pyconfig.h.in | 3 ++ 15 files changed, 218 insertions(+), 35 deletions(-) create mode 100644 Misc/NEWS.d/next/C API/2021-10-19-13-07-46.bpo-45522.kGAwmZ.rst diff --git a/Doc/whatsnew/3.11.rst b/Doc/whatsnew/3.11.rst index d5e8bc967a8166..74fc7536ea231c 100644 --- a/Doc/whatsnew/3.11.rst +++ b/Doc/whatsnew/3.11.rst @@ -481,6 +481,11 @@ Build Changes ``isinf()``, ``isnan()``, ``round()``. (Contributed by Victor Stinner in :issue:`45440`.) +* Freelists for object structs can now be disabled. A new :program:`configure` + option :option:`!--without-freelists` can be used to disable all freelists + except empty tuple singleton. + (Contributed by Christian Heimes in :issue:`45522`) + C API Changes ============= diff --git a/Include/internal/pycore_interp.h b/Include/internal/pycore_interp.h index 0e6edf4ec26702..64ac3abe00fa01 100644 --- a/Include/internal/pycore_interp.h +++ b/Include/internal/pycore_interp.h @@ -85,12 +85,31 @@ struct _Py_unicode_state { struct _Py_unicode_ids ids; }; +#ifndef WITH_FREELISTS +// without freelists +# define PyFloat_MAXFREELIST 0 +// for tuples only store empty tuple singleton +# define PyTuple_MAXSAVESIZE 1 +# define PyTuple_MAXFREELIST 1 +# define PyList_MAXFREELIST 0 +# define PyDict_MAXFREELIST 0 +# define PyFrame_MAXFREELIST 0 +# define _PyAsyncGen_MAXFREELIST 0 +# define PyContext_MAXFREELIST 0 +#endif + +#ifndef PyFloat_MAXFREELIST +# define PyFloat_MAXFREELIST 100 +#endif + struct _Py_float_state { +#if PyFloat_MAXFREELIST > 0 /* Special free list free_list is a singly-linked list of available PyFloatObjects, linked via abuse of their ob_type members. */ int numfree; PyFloatObject *free_list; +#endif }; /* Speed optimization to avoid frequent malloc/free of small tuples */ @@ -119,8 +138,10 @@ struct _Py_tuple_state { #endif struct _Py_list_state { +#if PyList_MAXFREELIST > 0 PyListObject *free_list[PyList_MAXFREELIST]; int numfree; +#endif }; #ifndef PyDict_MAXFREELIST @@ -128,17 +149,25 @@ struct _Py_list_state { #endif struct _Py_dict_state { +#if PyDict_MAXFREELIST > 0 /* Dictionary reuse scheme to save calls to malloc and free */ PyDictObject *free_list[PyDict_MAXFREELIST]; int numfree; PyDictKeysObject *keys_free_list[PyDict_MAXFREELIST]; int keys_numfree; +#endif }; +#ifndef PyFrame_MAXFREELIST +# define PyFrame_MAXFREELIST 200 +#endif + struct _Py_frame_state { +#if PyFrame_MAXFREELIST > 0 PyFrameObject *free_list; /* number of frames currently in free_list */ int numfree; +#endif }; #ifndef _PyAsyncGen_MAXFREELIST @@ -146,6 +175,7 @@ struct _Py_frame_state { #endif struct _Py_async_gen_state { +#if _PyAsyncGen_MAXFREELIST > 0 /* Freelists boost performance 6-10%; they also reduce memory fragmentation, as _PyAsyncGenWrappedValue and PyAsyncGenASend are short-living objects that are instantiated for every @@ -155,12 +185,19 @@ struct _Py_async_gen_state { struct PyAsyncGenASend* asend_freelist[_PyAsyncGen_MAXFREELIST]; int asend_numfree; +#endif }; +#ifndef PyContext_MAXFREELIST +# define PyContext_MAXFREELIST 255 +#endif + struct _Py_context_state { +#if PyContext_MAXFREELIST > 0 // List of free PyContext objects PyContext *freelist; int numfree; +#endif }; struct _Py_exc_state { diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py index 2182898837e502..b0688e1e605fe5 100644 --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -825,7 +825,18 @@ def test_debugmallocstats(self): from test.support.script_helper import assert_python_ok args = ['-c', 'import sys; sys._debugmallocstats()'] ret, out, err = assert_python_ok(*args) - self.assertIn(b"free PyDictObjects", err) + + # Output of sys._debugmallocstats() depends on configure flags. + # The sysconfig vars are not available on Windows. + if sys.platform != "win32": + with_freelists = sysconfig.get_config_var("WITH_FREELISTS") + with_pymalloc = sysconfig.get_config_var("WITH_PYMALLOC") + if with_freelists: + self.assertIn(b"free PyDictObjects", err) + if with_pymalloc: + self.assertIn(b'Small block threshold', err) + if not with_freelists and not with_pymalloc: + self.assertFalse(err) # The function has no parameter self.assertRaises(TypeError, sys._debugmallocstats, True) diff --git a/Misc/NEWS.d/next/C API/2021-10-19-13-07-46.bpo-45522.kGAwmZ.rst b/Misc/NEWS.d/next/C API/2021-10-19-13-07-46.bpo-45522.kGAwmZ.rst new file mode 100644 index 00000000000000..658261f8ce693b --- /dev/null +++ b/Misc/NEWS.d/next/C API/2021-10-19-13-07-46.bpo-45522.kGAwmZ.rst @@ -0,0 +1,2 @@ +The internal freelists for frame, float, list, dict, async generators, and +context objects can now be disabled. diff --git a/Objects/dictobject.c b/Objects/dictobject.c index 3d6e4c1e17e1f0..6dcd5a1d19715e 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -240,17 +240,20 @@ uint64_t _pydict_global_version = 0; #include "clinic/dictobject.c.h" +#if PyDict_MAXFREELIST > 0 static struct _Py_dict_state * get_dict_state(void) { PyInterpreterState *interp = _PyInterpreterState_GET(); return &interp->dict_state; } +#endif void _PyDict_ClearFreeList(PyInterpreterState *interp) { +#if PyDict_MAXFREELIST > 0 struct _Py_dict_state *state = &interp->dict_state; while (state->numfree) { PyDictObject *op = state->free_list[--state->numfree]; @@ -260,6 +263,7 @@ _PyDict_ClearFreeList(PyInterpreterState *interp) while (state->keys_numfree) { PyObject_Free(state->keys_free_list[--state->keys_numfree]); } +#endif } @@ -267,7 +271,7 @@ void _PyDict_Fini(PyInterpreterState *interp) { _PyDict_ClearFreeList(interp); -#ifdef Py_DEBUG +#if defined(Py_DEBUG) && PyDict_MAXFREELIST > 0 struct _Py_dict_state *state = &interp->dict_state; state->numfree = -1; state->keys_numfree = -1; @@ -279,9 +283,11 @@ _PyDict_Fini(PyInterpreterState *interp) void _PyDict_DebugMallocStats(FILE *out) { +#if PyDict_MAXFREELIST > 0 struct _Py_dict_state *state = get_dict_state(); _PyDebugAllocatorStats(out, "free PyDictObject", state->numfree, sizeof(PyDictObject)); +#endif } #define DK_MASK(dk) (DK_SIZE(dk)-1) @@ -570,6 +576,7 @@ new_keys_object(uint8_t log2_size) es = sizeof(Py_ssize_t); } +#if PyDict_MAXFREELIST > 0 struct _Py_dict_state *state = get_dict_state(); #ifdef Py_DEBUG // new_keys_object() must not be called after _PyDict_Fini() @@ -579,6 +586,7 @@ new_keys_object(uint8_t log2_size) dk = state->keys_free_list[--state->keys_numfree]; } else +#endif { dk = PyObject_Malloc(sizeof(PyDictKeysObject) + (es< 0 struct _Py_dict_state *state = get_dict_state(); #ifdef Py_DEBUG // free_keys_object() must not be called after _PyDict_Fini() @@ -620,6 +629,7 @@ free_keys_object(PyDictKeysObject *keys) state->keys_free_list[state->keys_numfree++] = keys; return; } +#endif PyObject_Free(keys); } @@ -638,6 +648,7 @@ new_dict(PyDictKeysObject *keys, PyDictValues *values, Py_ssize_t used, int free { PyDictObject *mp; assert(keys != NULL); +#if PyDict_MAXFREELIST > 0 struct _Py_dict_state *state = get_dict_state(); #ifdef Py_DEBUG // new_dict() must not be called after _PyDict_Fini() @@ -649,7 +660,9 @@ new_dict(PyDictKeysObject *keys, PyDictValues *values, Py_ssize_t used, int free assert (Py_IS_TYPE(mp, &PyDict_Type)); _Py_NewReference((PyObject *)mp); } - else { + else +#endif + { mp = PyObject_GC_New(PyDictObject, &PyDict_Type); if (mp == NULL) { dictkeys_decref(keys); @@ -1259,6 +1272,7 @@ dictresize(PyDictObject *mp, uint8_t log2_newsize) #ifdef Py_REF_DEBUG _Py_RefTotal--; #endif +#if PyDict_MAXFREELIST > 0 struct _Py_dict_state *state = get_dict_state(); #ifdef Py_DEBUG // dictresize() must not be called after _PyDict_Fini() @@ -1269,7 +1283,9 @@ dictresize(PyDictObject *mp, uint8_t log2_newsize) { state->keys_free_list[state->keys_numfree++] = oldkeys; } - else { + else +#endif + { PyObject_Free(oldkeys); } } @@ -1987,6 +2003,7 @@ dict_dealloc(PyDictObject *mp) assert(keys->dk_refcnt == 1); dictkeys_decref(keys); } +#if PyDict_MAXFREELIST > 0 struct _Py_dict_state *state = get_dict_state(); #ifdef Py_DEBUG // new_dict() must not be called after _PyDict_Fini() @@ -1995,7 +2012,9 @@ dict_dealloc(PyDictObject *mp) if (state->numfree < PyDict_MAXFREELIST && Py_IS_TYPE(mp, &PyDict_Type)) { state->free_list[state->numfree++] = mp; } - else { + else +#endif + { Py_TYPE(mp)->tp_free((PyObject *)mp); } Py_TRASHCAN_END diff --git a/Objects/floatobject.c b/Objects/floatobject.c index 1be31e38d4935e..7fc192e7201171 100644 --- a/Objects/floatobject.c +++ b/Objects/floatobject.c @@ -28,12 +28,14 @@ class float "PyObject *" "&PyFloat_Type" #endif +#if PyFloat_MAXFREELIST > 0 static struct _Py_float_state * get_float_state(void) { PyInterpreterState *interp = _PyInterpreterState_GET(); return &interp->float_state; } +#endif double @@ -126,8 +128,10 @@ PyFloat_GetInfo(void) PyObject * PyFloat_FromDouble(double fval) { + PyFloatObject *op; +#if PyFloat_MAXFREELIST > 0 struct _Py_float_state *state = get_float_state(); - PyFloatObject *op = state->free_list; + op = state->free_list; if (op != NULL) { #ifdef Py_DEBUG // PyFloat_FromDouble() must not be called after _PyFloat_Fini() @@ -136,7 +140,9 @@ PyFloat_FromDouble(double fval) state->free_list = (PyFloatObject *) Py_TYPE(op); state->numfree--; } - else { + else +#endif + { op = PyObject_Malloc(sizeof(PyFloatObject)); if (!op) { return PyErr_NoMemory(); @@ -233,6 +239,7 @@ PyFloat_FromString(PyObject *v) static void float_dealloc(PyFloatObject *op) { +#if PyFloat_MAXFREELIST > 0 if (PyFloat_CheckExact(op)) { struct _Py_float_state *state = get_float_state(); #ifdef Py_DEBUG @@ -247,7 +254,9 @@ float_dealloc(PyFloatObject *op) Py_SET_TYPE(op, (PyTypeObject *)state->free_list); state->free_list = op; } - else { + else +#endif + { Py_TYPE(op)->tp_free((PyObject *)op); } } @@ -2036,6 +2045,7 @@ _PyFloat_InitTypes(void) void _PyFloat_ClearFreeList(PyInterpreterState *interp) { +#if PyFloat_MAXFREELIST > 0 struct _Py_float_state *state = &interp->float_state; PyFloatObject *f = state->free_list; while (f != NULL) { @@ -2045,13 +2055,14 @@ _PyFloat_ClearFreeList(PyInterpreterState *interp) } state->free_list = NULL; state->numfree = 0; +#endif } void _PyFloat_Fini(PyInterpreterState *interp) { _PyFloat_ClearFreeList(interp); -#ifdef Py_DEBUG +#if defined(Py_DEBUG) && PyFloat_MAXFREELIST > 0 struct _Py_float_state *state = &interp->float_state; state->numfree = -1; #endif @@ -2061,10 +2072,12 @@ _PyFloat_Fini(PyInterpreterState *interp) void _PyFloat_DebugMallocStats(FILE *out) { +#if PyFloat_MAXFREELIST > 0 struct _Py_float_state *state = get_float_state(); _PyDebugAllocatorStats(out, "free PyFloatObject", state->numfree, sizeof(PyFloatObject)); +#endif } diff --git a/Objects/frameobject.c b/Objects/frameobject.c index 5271790f018af5..ffe19b3d994007 100644 --- a/Objects/frameobject.c +++ b/Objects/frameobject.c @@ -19,12 +19,14 @@ static PyMemberDef frame_memberlist[] = { {NULL} /* Sentinel */ }; +#if PyFrame_MAXFREELIST > 0 static struct _Py_frame_state * get_frame_state(void) { PyInterpreterState *interp = _PyInterpreterState_GET(); return &interp->frame; } +#endif static PyObject * @@ -607,9 +609,6 @@ static PyGetSetDef frame_getsetlist[] = { f_back next item on free list, or NULL */ -/* max value for numfree */ -#define PyFrame_MAXFREELIST 200 - static void _Py_HOT_FUNCTION frame_dealloc(PyFrameObject *f) { @@ -638,6 +637,7 @@ frame_dealloc(PyFrameObject *f) } Py_CLEAR(f->f_back); Py_CLEAR(f->f_trace); +#if PyFrame_MAXFREELIST > 0 struct _Py_frame_state *state = get_frame_state(); #ifdef Py_DEBUG // frame_dealloc() must not be called after _PyFrame_Fini() @@ -648,7 +648,9 @@ frame_dealloc(PyFrameObject *f) f->f_back = state->free_list; state->free_list = f; } - else { + else +#endif + { PyObject_GC_Del(f); } @@ -801,8 +803,10 @@ static inline PyFrameObject* frame_alloc(InterpreterFrame *frame, int owns) { PyFrameObject *f; +#if PyFrame_MAXFREELIST > 0 struct _Py_frame_state *state = get_frame_state(); if (state->free_list == NULL) +#endif { f = PyObject_GC_New(PyFrameObject, &PyFrame_Type); if (f == NULL) { @@ -816,7 +820,9 @@ frame_alloc(InterpreterFrame *frame, int owns) return NULL; } } - else { +#if PyFrame_MAXFREELIST > 0 + else + { #ifdef Py_DEBUG // frame_alloc() must not be called after _PyFrame_Fini() assert(state->numfree != -1); @@ -827,6 +833,7 @@ frame_alloc(InterpreterFrame *frame, int owns) state->free_list = state->free_list->f_back; _Py_NewReference((PyObject *)f); } +#endif f->f_frame = frame; f->f_own_locals_memory = owns; return f; @@ -1069,6 +1076,7 @@ PyFrame_LocalsToFast(PyFrameObject *f, int clear) void _PyFrame_ClearFreeList(PyInterpreterState *interp) { +#if PyFrame_MAXFREELIST > 0 struct _Py_frame_state *state = &interp->frame; while (state->free_list != NULL) { PyFrameObject *f = state->free_list; @@ -1077,13 +1085,14 @@ _PyFrame_ClearFreeList(PyInterpreterState *interp) --state->numfree; } assert(state->numfree == 0); +#endif } void _PyFrame_Fini(PyInterpreterState *interp) { _PyFrame_ClearFreeList(interp); -#ifdef Py_DEBUG +#if defined(Py_DEBUG) && PyFrame_MAXFREELIST > 0 struct _Py_frame_state *state = &interp->frame; state->numfree = -1; #endif @@ -1093,10 +1102,12 @@ _PyFrame_Fini(PyInterpreterState *interp) void _PyFrame_DebugMallocStats(FILE *out) { +#if PyFrame_MAXFREELIST > 0 struct _Py_frame_state *state = get_frame_state(); _PyDebugAllocatorStats(out, "free PyFrameObject", state->numfree, sizeof(PyFrameObject)); +#endif } diff --git a/Objects/genobject.c b/Objects/genobject.c index f91f367f9a6874..8bf55155eab1c4 100644 --- a/Objects/genobject.c +++ b/Objects/genobject.c @@ -1567,12 +1567,14 @@ PyTypeObject PyAsyncGen_Type = { }; +#if _PyAsyncGen_MAXFREELIST > 0 static struct _Py_async_gen_state * get_async_gen_state(void) { PyInterpreterState *interp = _PyInterpreterState_GET(); return &interp->async_gen; } +#endif PyObject * @@ -1595,6 +1597,7 @@ PyAsyncGen_New(PyFrameObject *f, PyObject *name, PyObject *qualname) void _PyAsyncGen_ClearFreeLists(PyInterpreterState *interp) { +#if _PyAsyncGen_MAXFREELIST > 0 struct _Py_async_gen_state *state = &interp->async_gen; while (state->value_numfree) { @@ -1610,13 +1613,14 @@ _PyAsyncGen_ClearFreeLists(PyInterpreterState *interp) assert(Py_IS_TYPE(o, &_PyAsyncGenASend_Type)); PyObject_GC_Del(o); } +#endif } void _PyAsyncGen_Fini(PyInterpreterState *interp) { _PyAsyncGen_ClearFreeLists(interp); -#ifdef Py_DEBUG +#if defined(Py_DEBUG) && _PyAsyncGen_MAXFREELIST > 0 struct _Py_async_gen_state *state = &interp->async_gen; state->value_numfree = -1; state->asend_numfree = -1; @@ -1663,6 +1667,7 @@ async_gen_asend_dealloc(PyAsyncGenASend *o) _PyObject_GC_UNTRACK((PyObject *)o); Py_CLEAR(o->ags_gen); Py_CLEAR(o->ags_sendval); +#if _PyAsyncGen_MAXFREELIST > 0 struct _Py_async_gen_state *state = get_async_gen_state(); #ifdef Py_DEBUG // async_gen_asend_dealloc() must not be called after _PyAsyncGen_Fini() @@ -1672,7 +1677,9 @@ async_gen_asend_dealloc(PyAsyncGenASend *o) assert(PyAsyncGenASend_CheckExact(o)); state->asend_freelist[state->asend_numfree++] = o; } - else { + else +#endif + { PyObject_GC_Del(o); } } @@ -1825,6 +1832,7 @@ static PyObject * async_gen_asend_new(PyAsyncGenObject *gen, PyObject *sendval) { PyAsyncGenASend *o; +#if _PyAsyncGen_MAXFREELIST > 0 struct _Py_async_gen_state *state = get_async_gen_state(); #ifdef Py_DEBUG // async_gen_asend_new() must not be called after _PyAsyncGen_Fini() @@ -1835,7 +1843,9 @@ async_gen_asend_new(PyAsyncGenObject *gen, PyObject *sendval) o = state->asend_freelist[state->asend_numfree]; _Py_NewReference((PyObject *)o); } - else { + else +#endif + { o = PyObject_GC_New(PyAsyncGenASend, &_PyAsyncGenASend_Type); if (o == NULL) { return NULL; @@ -1863,6 +1873,7 @@ async_gen_wrapped_val_dealloc(_PyAsyncGenWrappedValue *o) { _PyObject_GC_UNTRACK((PyObject *)o); Py_CLEAR(o->agw_val); +#if _PyAsyncGen_MAXFREELIST > 0 struct _Py_async_gen_state *state = get_async_gen_state(); #ifdef Py_DEBUG // async_gen_wrapped_val_dealloc() must not be called after _PyAsyncGen_Fini() @@ -1872,7 +1883,9 @@ async_gen_wrapped_val_dealloc(_PyAsyncGenWrappedValue *o) assert(_PyAsyncGenWrappedValue_CheckExact(o)); state->value_freelist[state->value_numfree++] = o; } - else { + else +#endif + { PyObject_GC_Del(o); } } @@ -1936,6 +1949,7 @@ _PyAsyncGenValueWrapperNew(PyObject *val) _PyAsyncGenWrappedValue *o; assert(val); +#if _PyAsyncGen_MAXFREELIST > 0 struct _Py_async_gen_state *state = get_async_gen_state(); #ifdef Py_DEBUG // _PyAsyncGenValueWrapperNew() must not be called after _PyAsyncGen_Fini() @@ -1947,7 +1961,9 @@ _PyAsyncGenValueWrapperNew(PyObject *val) assert(_PyAsyncGenWrappedValue_CheckExact(o)); _Py_NewReference((PyObject*)o); } - else { + else +#endif + { o = PyObject_GC_New(_PyAsyncGenWrappedValue, &_PyAsyncGenWrappedValue_Type); if (o == NULL) { diff --git a/Objects/listobject.c b/Objects/listobject.c index ed5324155f6275..e0cae87f45781d 100644 --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -19,13 +19,14 @@ class list "PyListObject *" "&PyList_Type" #include "clinic/listobject.c.h" - +#if PyList_MAXFREELIST > 0 static struct _Py_list_state * get_list_state(void) { PyInterpreterState *interp = _PyInterpreterState_GET(); return &interp->list; } +#endif /* Ensure ob_item has room for at least newsize elements, and set @@ -108,19 +109,21 @@ list_preallocate_exact(PyListObject *self, Py_ssize_t size) void _PyList_ClearFreeList(PyInterpreterState *interp) { +#if PyList_MAXFREELIST > 0 struct _Py_list_state *state = &interp->list; while (state->numfree) { PyListObject *op = state->free_list[--state->numfree]; assert(PyList_CheckExact(op)); PyObject_GC_Del(op); } +#endif } void _PyList_Fini(PyInterpreterState *interp) { _PyList_ClearFreeList(interp); -#ifdef Py_DEBUG +#if defined(Py_DEBUG) && PyList_MAXFREELIST > 0 struct _Py_list_state *state = &interp->list; state->numfree = -1; #endif @@ -130,32 +133,38 @@ _PyList_Fini(PyInterpreterState *interp) void _PyList_DebugMallocStats(FILE *out) { +#if PyList_MAXFREELIST > 0 struct _Py_list_state *state = get_list_state(); _PyDebugAllocatorStats(out, "free PyListObject", state->numfree, sizeof(PyListObject)); +#endif } PyObject * PyList_New(Py_ssize_t size) { + PyListObject *op; + if (size < 0) { PyErr_BadInternalCall(); return NULL; } +#if PyList_MAXFREELIST > 0 struct _Py_list_state *state = get_list_state(); - PyListObject *op; #ifdef Py_DEBUG // PyList_New() must not be called after _PyList_Fini() assert(state->numfree != -1); #endif - if (state->numfree) { + if (PyList_MAXFREELIST && state->numfree) { state->numfree--; op = state->free_list[state->numfree]; _Py_NewReference((PyObject *)op); } - else { + else +#endif + { op = PyObject_GC_New(PyListObject, &PyList_Type); if (op == NULL) { return NULL; @@ -344,6 +353,7 @@ list_dealloc(PyListObject *op) } PyMem_Free(op->ob_item); } +#if PyList_MAXFREELIST > 0 struct _Py_list_state *state = get_list_state(); #ifdef Py_DEBUG // list_dealloc() must not be called after _PyList_Fini() @@ -352,7 +362,9 @@ list_dealloc(PyListObject *op) if (state->numfree < PyList_MAXFREELIST && PyList_CheckExact(op)) { state->free_list[state->numfree++] = op; } - else { + else +#endif + { Py_TYPE(op)->tp_free((PyObject *)op); } Py_TRASHCAN_END diff --git a/Objects/tupleobject.c b/Objects/tupleobject.c index 051683086ea2c5..e9d1b5926abb37 100644 --- a/Objects/tupleobject.c +++ b/Objects/tupleobject.c @@ -66,7 +66,8 @@ tuple_alloc(Py_ssize_t size) return NULL; } -#if PyTuple_MAXSAVESIZE > 0 +// Check for max save size > 1. Empty tuple singleton is special case. +#if PyTuple_MAXSAVESIZE > 1 struct _Py_tuple_state *state = get_tuple_state(); #ifdef Py_DEBUG // tuple_alloc() must not be called after _PyTuple_Fini() diff --git a/PC/pyconfig.h b/PC/pyconfig.h index bb55ff4fe14c47..b3e73d4dabefed 100644 --- a/PC/pyconfig.h +++ b/PC/pyconfig.h @@ -463,6 +463,9 @@ Py_NO_ENABLE_SHARED to find out. Also support MS_NO_COREDLL for b/w compat */ /* Use Python's own small-block memory-allocator. */ #define WITH_PYMALLOC 1 +/* Define if you want to compile in object freelists optimization */ +#define WITH_FREELISTS 1 + /* Define if you have clock. */ /* #define HAVE_CLOCK */ diff --git a/Python/context.c b/Python/context.c index d78f7f993bb89e..a20ec7123731c2 100644 --- a/Python/context.c +++ b/Python/context.c @@ -9,9 +9,6 @@ #include "structmember.h" // PyMemberDef -#define CONTEXT_FREELIST_MAXLEN 255 - - #include "clinic/context.c.h" /*[clinic input] module _contextvars @@ -66,12 +63,14 @@ static int contextvar_del(PyContextVar *var); +#if PyContext_MAXFREELIST > 0 static struct _Py_context_state * get_context_state(void) { PyInterpreterState *interp = _PyInterpreterState_GET(); return &interp->context; } +#endif PyObject * @@ -340,8 +339,9 @@ class _contextvars.Context "PyContext *" "&PyContext_Type" static inline PyContext * _context_alloc(void) { - struct _Py_context_state *state = get_context_state(); PyContext *ctx; +#if PyContext_MAXFREELIST > 0 + struct _Py_context_state *state = get_context_state(); #ifdef Py_DEBUG // _context_alloc() must not be called after _PyContext_Fini() assert(state->numfree != -1); @@ -353,7 +353,9 @@ _context_alloc(void) ctx->ctx_weakreflist = NULL; _Py_NewReference((PyObject *)ctx); } - else { + else +#endif + { ctx = PyObject_GC_New(PyContext, &PyContext_Type); if (ctx == NULL) { return NULL; @@ -469,17 +471,20 @@ context_tp_dealloc(PyContext *self) } (void)context_tp_clear(self); +#if PyContext_MAXFREELIST > 0 struct _Py_context_state *state = get_context_state(); #ifdef Py_DEBUG // _context_alloc() must not be called after _PyContext_Fini() assert(state->numfree != -1); #endif - if (state->numfree < CONTEXT_FREELIST_MAXLEN) { + if (state->numfree < PyContext_MAXFREELIST) { state->numfree++; self->ctx_weakreflist = (PyObject *)state->freelist; state->freelist = self; } - else { + else +#endif + { Py_TYPE(self)->tp_free(self); } } @@ -1289,6 +1294,7 @@ get_token_missing(void) void _PyContext_ClearFreeList(PyInterpreterState *interp) { +#if PyContext_MAXFREELIST > 0 struct _Py_context_state *state = &interp->context; for (; state->numfree; state->numfree--) { PyContext *ctx = state->freelist; @@ -1296,6 +1302,7 @@ _PyContext_ClearFreeList(PyInterpreterState *interp) ctx->ctx_weakreflist = NULL; PyObject_GC_Del(ctx); } +#endif } @@ -1306,7 +1313,7 @@ _PyContext_Fini(PyInterpreterState *interp) Py_CLEAR(_token_missing); } _PyContext_ClearFreeList(interp); -#ifdef Py_DEBUG +#if defined(Py_DEBUG) && PyContext_MAXFREELIST > 0 struct _Py_context_state *state = &interp->context; state->numfree = -1; #endif diff --git a/configure b/configure index ec7c72c7f3276d..198b0703fd6af1 100755 --- a/configure +++ b/configure @@ -846,6 +846,7 @@ with_dbmliborder enable_ipv6 with_doc_strings with_pymalloc +with_freelists with_c_locale_coercion with_valgrind with_dtrace @@ -1588,6 +1589,7 @@ Optional Packages: names `ndbm', `gdbm' and `bdb'. --with-doc-strings enable documentation strings (default is yes) --with-pymalloc enable specialized mallocs (default is yes) + --with-freelists enable object freelists (default is yes) --with-c-locale-coercion enable C locale coercion to a UTF-8 based locale (default is yes) @@ -11728,6 +11730,30 @@ fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_pymalloc" >&5 $as_echo "$with_pymalloc" >&6; } +# Check whether objects such as float, tuple and dict are using +# freelists to optimization memory allocation. +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for --with-freelists" >&5 +$as_echo_n "checking for --with-freelists... " >&6; } + +# Check whether --with-freelists was given. +if test "${with_freelists+set}" = set; then : + withval=$with_freelists; +fi + + +if test -z "$with_freelists" +then + with_freelists="yes" +fi +if test "$with_freelists" != "no" +then + +$as_echo "#define WITH_FREELISTS 1" >>confdefs.h + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_freelists" >&5 +$as_echo "$with_freelists" >&6; } + # Check for --with-c-locale-coercion { $as_echo "$as_me:${as_lineno-$LINENO}: checking for --with-c-locale-coercion" >&5 $as_echo_n "checking for --with-c-locale-coercion... " >&6; } diff --git a/configure.ac b/configure.ac index c0259524756f2c..edda08daac117e 100644 --- a/configure.ac +++ b/configure.ac @@ -3616,6 +3616,23 @@ then fi AC_MSG_RESULT($with_pymalloc) +# Check whether objects such as float, tuple and dict are using +# freelists to optimization memory allocation. +AC_MSG_CHECKING(for --with-freelists) +AC_ARG_WITH(freelists, + AS_HELP_STRING([--with-freelists], [enable object freelists (default is yes)])) + +if test -z "$with_freelists" +then + with_freelists="yes" +fi +if test "$with_freelists" != "no" +then + AC_DEFINE(WITH_FREELISTS, 1, + [Define if you want to compile in object freelists optimization]) +fi +AC_MSG_RESULT($with_freelists) + # Check for --with-c-locale-coercion AC_MSG_CHECKING(for --with-c-locale-coercion) AC_ARG_WITH(c-locale-coercion, diff --git a/pyconfig.h.in b/pyconfig.h.in index a426e8effddb9f..081ea61bae8347 100644 --- a/pyconfig.h.in +++ b/pyconfig.h.in @@ -1555,6 +1555,9 @@ /* Define to build the readline module against Editline. */ #undef WITH_EDITLINE +/* Define if you want to compile in object freelists optimization */ +#undef WITH_FREELISTS + /* Define to 1 if libintl is needed for locale functions. */ #undef WITH_LIBINTL