From a136903a35d45ac679d0b5ac17280f72b36314a9 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Thu, 21 Oct 2021 15:37:04 +0100 Subject: [PATCH 1/7] Use optimized freelist for ints and floats. --- Include/internal/pycore_pymem.h | 88 +++++++++++++++++++++++++++++++++ Objects/floatobject.c | 32 ++---------- Objects/longobject.c | 21 ++++++-- Objects/obmalloc.c | 52 +++++++++++++++++++ 4 files changed, 161 insertions(+), 32 deletions(-) diff --git a/Include/internal/pycore_pymem.h b/Include/internal/pycore_pymem.h index d70deee710e30d4..35dcc0bde115560 100644 --- a/Include/internal/pycore_pymem.h +++ b/Include/internal/pycore_pymem.h @@ -100,6 +100,94 @@ void *_PyObject_VirtualAlloc(size_t size); void _PyObject_VirtualFree(void *, size_t size); +/* Free lists. + * + * Free lists have a pointer to their first entry and + * the amunt of space available allowing fast checks + * for emptiness and fullness. + * When empty they are half filled and when full they are + * completely emptied. This helps the underlying allocator + * avoid fragmentation and helps performance. + */ + +typedef struct _freelist { + void *ptr; + uint32_t space; + uint16_t size; + uint16_t capacity; +} _PyFreeList; + +extern int _PyObject_BulkFree(void *ptr); + +extern void *_PyFreeList_HalfFillAndAllocate(_PyFreeList *list); +extern void _PyFreeList_FreeToFull(_PyFreeList *list, void *ptr); + +static inline void * +_PyFreeList_Alloc(_PyFreeList *list) { + if (list->ptr != NULL) { + void *result = list->ptr; + list->ptr = *((void **)result); + list->space++; + return result; + } + return _PyFreeList_HalfFillAndAllocate(list); +} + +static inline void +_PyFreeList_Free(_PyFreeList *list, void *ptr) { + if (list->space) { + *((void **)ptr) = list->ptr; + list->ptr = ptr; + list->space--; + return; + } + _PyFreeList_FreeToFull(list, ptr); +} + +/* + * -- Main tunable settings section -- + */ + +/* + * Alignment of addresses returned to the user. 8-bytes alignment works + * on most current architectures (with 32-bit or 64-bit address buses). + * The alignment value is also used for grouping small requests in size + * classes spaced ALIGNMENT bytes apart. + * + * You shouldn't change this unless you know what you are doing. + */ + +#if SIZEOF_VOID_P > 4 +#define ALIGNMENT 16 /* must be 2^N */ +#define ALIGNMENT_SHIFT 4 +#else +#define ALIGNMENT 8 /* must be 2^N */ +#define ALIGNMENT_SHIFT 3 +#endif + +/* Return the number of bytes in size class I, as a uint. */ +#define INDEX2SIZE(I) (((uint)(I) + 1) << ALIGNMENT_SHIFT) + +/* + * Max size threshold below which malloc requests are considered to be + * small enough in order to use preallocated memory pools. You can tune + * this value according to your application behaviour and memory needs. + * + * Note: a size threshold of 512 guarantees that newly created dictionaries + * will be allocated from preallocated memory pools on 64-bit. + * + * The following invariants must hold: + * 1) ALIGNMENT <= SMALL_REQUEST_THRESHOLD <= 512 + * 2) SMALL_REQUEST_THRESHOLD is evenly divisible by ALIGNMENT + * + * Although not required, for better performance and space efficiency, + * it is recommended that SMALL_REQUEST_THRESHOLD is set to a power of 2. + */ +#define SMALL_REQUEST_THRESHOLD 512 +#define NB_SMALL_SIZE_CLASSES (SMALL_REQUEST_THRESHOLD / ALIGNMENT) + +extern _PyFreeList _Py_small_object_freelist; + #ifdef __cplusplus } #endif diff --git a/Objects/floatobject.c b/Objects/floatobject.c index 1be31e38d4935e8..1e001e43644b43b 100644 --- a/Objects/floatobject.c +++ b/Objects/floatobject.c @@ -10,6 +10,7 @@ #include "pycore_long.h" // _PyLong_GetOne() #include "pycore_object.h" // _PyObject_Init() #include "pycore_pymath.h" // _Py_ADJUST_ERANGE1() +#include "pycore_pymem.h" // Free lists #include "pycore_pystate.h" // _PyInterpreterState_GET() #include @@ -126,21 +127,9 @@ PyFloat_GetInfo(void) PyObject * PyFloat_FromDouble(double fval) { - struct _Py_float_state *state = get_float_state(); - PyFloatObject *op = state->free_list; - if (op != NULL) { -#ifdef Py_DEBUG - // PyFloat_FromDouble() must not be called after _PyFloat_Fini() - assert(state->numfree != -1); -#endif - state->free_list = (PyFloatObject *) Py_TYPE(op); - state->numfree--; - } - else { - op = PyObject_Malloc(sizeof(PyFloatObject)); - if (!op) { - return PyErr_NoMemory(); - } + PyFloatObject *op = (PyFloatObject *)_PyFreeList_Alloc(&_Py_small_object_freelist); + if (op == NULL) { + return PyErr_NoMemory(); } _PyObject_Init((PyObject*)op, &PyFloat_Type); op->ob_fval = fval; @@ -234,18 +223,7 @@ static void float_dealloc(PyFloatObject *op) { if (PyFloat_CheckExact(op)) { - struct _Py_float_state *state = get_float_state(); -#ifdef Py_DEBUG - // float_dealloc() must not be called after _PyFloat_Fini() - assert(state->numfree != -1); -#endif - if (state->numfree >= PyFloat_MAXFREELIST) { - PyObject_Free(op); - return; - } - state->numfree++; - Py_SET_TYPE(op, (PyTypeObject *)state->free_list); - state->free_list = op; + _PyFreeList_Free(&_Py_small_object_freelist, op); } else { Py_TYPE(op)->tp_free((PyObject *)op); diff --git a/Objects/longobject.c b/Objects/longobject.c index 5325d1852bc029f..4a8f6fac70e1bdf 100644 --- a/Objects/longobject.c +++ b/Objects/longobject.c @@ -7,6 +7,7 @@ #include "pycore_interp.h" // _PY_NSMALLPOSINTS #include "pycore_long.h" // __PyLong_GetSmallInt_internal() #include "pycore_object.h" // _PyObject_InitVar() +#include "pycore_pymem.h" // Free lists #include "pycore_pystate.h" // _Py_IsMainInterpreter() #include @@ -170,11 +171,9 @@ _PyLong_FromMedium(sdigit x) { assert(!IS_SMALL_INT(x)); assert(is_medium_int(x)); - /* We could use a freelist here */ - PyLongObject *v = PyObject_Malloc(sizeof(PyLongObject)); + PyLongObject *v = (PyLongObject *)_PyFreeList_Alloc(&_Py_small_object_freelist); if (v == NULL) { - PyErr_NoMemory(); - return NULL; + return PyErr_NoMemory(); } Py_ssize_t sign = x < 0 ? -1: 1; digit abs_x = x < 0 ? -x : x; @@ -5743,12 +5742,24 @@ static PyNumberMethods long_as_number = { long_long, /* nb_index */ }; +static void +int_dealloc(PyLongObject *op) +{ + if (PyLong_CheckExact(op) && IS_MEDIUM_VALUE(op)) { + _PyFreeList_Free(&_Py_small_object_freelist, op); + } + else { + Py_TYPE(op)->tp_free((PyObject *)op); + } +} + + PyTypeObject PyLong_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) "int", /* tp_name */ offsetof(PyLongObject, ob_digit), /* tp_basicsize */ sizeof(digit), /* tp_itemsize */ - 0, /* tp_dealloc */ + (destructor)int_dealloc, /* tp_dealloc */ 0, /* tp_vectorcall_offset */ 0, /* tp_getattr */ 0, /* tp_setattr */ diff --git a/Objects/obmalloc.c b/Objects/obmalloc.c index d8d6f6dea0d5324..7d4228061902dcc 100644 --- a/Objects/obmalloc.c +++ b/Objects/obmalloc.c @@ -1957,6 +1957,38 @@ pymalloc_alloc(void *ctx, size_t nbytes) return (void *)bp; } +union _small_object { + PyFloatObject f; + PyLongObject l; +}; + +_PyFreeList _Py_small_object_freelist = { NULL, 256, sizeof(union _small_object), 256 }; + +void * +_PyFreeList_HalfFillAndAllocate(_PyFreeList *list) +{ + assert(list->ptr == NULL); + if (list->space < 4) { + return PyObject_Malloc(list->size); + } + uint32_t i = 0; + for (; i < list->space>>1; i++) { + void* ptr = PyObject_Malloc(list->size); + if (ptr == NULL) { + break; + } + *((void**)ptr) = list->ptr; + list->ptr = ptr; + } + if (i == 0) { + return NULL; + } + list->space -= (i-1); + void *result = list->ptr; + list->ptr = *((void **)result); + return result; +} + static void * _PyObject_Malloc(void *ctx, size_t nbytes) @@ -2253,6 +2285,26 @@ _PyObject_Free(void *ctx, void *p) } } +void +_PyFreeList_FreeToFull(_PyFreeList *list, void *ptr) +{ + assert(list->space == 0); + if (list->ptr == NULL) { + PyObject_Free(ptr); + return; + } + int space = 0; + void *head = list->ptr; + while (head) { + void *next = *((void**)head); + PyObject_Free(head); + head = next; + space++; + } + list->ptr = ptr; + *((void **)ptr) = NULL; + list->space = space-1; +} /* pymalloc realloc. From b82c39d30af3770e5e2e57a3f58aac25374e9dfc Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Fri, 22 Oct 2021 15:56:14 +0100 Subject: [PATCH 2/7] Tidy up --- Include/internal/pycore_floatobject.h | 3 -- Include/internal/pycore_pymem.h | 42 --------------------------- Objects/floatobject.c | 33 +-------------------- Objects/longobject.c | 5 +++- Objects/object.c | 1 - 5 files changed, 5 insertions(+), 79 deletions(-) diff --git a/Include/internal/pycore_floatobject.h b/Include/internal/pycore_floatobject.h index 18227c9e36925d2..11d32588f6f6592 100644 --- a/Include/internal/pycore_floatobject.h +++ b/Include/internal/pycore_floatobject.h @@ -57,9 +57,6 @@ PyAPI_FUNC(double) _PyFloat_Unpack4(const unsigned char *p, int le); PyAPI_FUNC(double) _PyFloat_Unpack8(const unsigned char *p, int le); -PyAPI_FUNC(void) _PyFloat_DebugMallocStats(FILE* out); - - /* Format the object based on the format_spec, as defined in PEP 3101 (Advanced String Formatting). */ PyAPI_FUNC(int) _PyFloat_FormatAdvancedWriter( diff --git a/Include/internal/pycore_pymem.h b/Include/internal/pycore_pymem.h index 109ae6293388a0a..1999596c20145c5 100644 --- a/Include/internal/pycore_pymem.h +++ b/Include/internal/pycore_pymem.h @@ -144,48 +144,6 @@ _PyFreeList_Free(_PyFreeList *list, void *ptr) { _PyFreeList_FreeToFull(list, ptr); } -/* - * -- Main tunable settings section -- - */ - -/* - * Alignment of addresses returned to the user. 8-bytes alignment works - * on most current architectures (with 32-bit or 64-bit address buses). - * The alignment value is also used for grouping small requests in size - * classes spaced ALIGNMENT bytes apart. - * - * You shouldn't change this unless you know what you are doing. - */ - -#if SIZEOF_VOID_P > 4 -#define ALIGNMENT 16 /* must be 2^N */ -#define ALIGNMENT_SHIFT 4 -#else -#define ALIGNMENT 8 /* must be 2^N */ -#define ALIGNMENT_SHIFT 3 -#endif - -/* Return the number of bytes in size class I, as a uint. */ -#define INDEX2SIZE(I) (((uint)(I) + 1) << ALIGNMENT_SHIFT) - -/* - * Max size threshold below which malloc requests are considered to be - * small enough in order to use preallocated memory pools. You can tune - * this value according to your application behaviour and memory needs. - * - * Note: a size threshold of 512 guarantees that newly created dictionaries - * will be allocated from preallocated memory pools on 64-bit. - * - * The following invariants must hold: - * 1) ALIGNMENT <= SMALL_REQUEST_THRESHOLD <= 512 - * 2) SMALL_REQUEST_THRESHOLD is evenly divisible by ALIGNMENT - * - * Although not required, for better performance and space efficiency, - * it is recommended that SMALL_REQUEST_THRESHOLD is set to a power of 2. - */ -#define SMALL_REQUEST_THRESHOLD 512 -#define NB_SMALL_SIZE_CLASSES (SMALL_REQUEST_THRESHOLD / ALIGNMENT) - #if WITH_FREELISTS extern _PyFreeList _Py_small_object_freelist; #endif diff --git a/Objects/floatobject.c b/Objects/floatobject.c index 17a63a1c6355890..bc0b9c576d827ec 100644 --- a/Objects/floatobject.c +++ b/Objects/floatobject.c @@ -24,21 +24,6 @@ class float "PyObject *" "&PyFloat_Type" #include "clinic/floatobject.c.h" -#ifndef PyFloat_MAXFREELIST -# define PyFloat_MAXFREELIST 100 -#endif - - -#if PyFloat_MAXFREELIST > 0 -static struct _Py_float_state * -get_float_state(void) -{ - PyInterpreterState *interp = _PyInterpreterState_GET(); - return &interp->float_state; -} -#endif - - double PyFloat_GetMax(void) { @@ -228,7 +213,7 @@ PyFloat_FromString(PyObject *v) static void float_dealloc(PyFloatObject *op) { -#if PyFloat_MAXFREELIST > 0 +#if WITH_FREELISTS if (PyFloat_CheckExact(op)) { _PyFreeList_Free(&_Py_small_object_freelist, op); } @@ -2040,22 +2025,6 @@ void _PyFloat_Fini(PyInterpreterState *interp) { _PyFloat_ClearFreeList(interp); -#if defined(Py_DEBUG) && PyFloat_MAXFREELIST > 0 - struct _Py_float_state *state = &interp->float_state; - state->numfree = -1; -#endif -} - -/* Print summary info about the state of the optimized allocator */ -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/longobject.c b/Objects/longobject.c index cfb2dab7946dcda..04d522e869fbddf 100644 --- a/Objects/longobject.c +++ b/Objects/longobject.c @@ -5749,10 +5749,13 @@ static PyNumberMethods long_as_number = { static void int_dealloc(PyLongObject *op) { +#if WITH_FREELISTS if (PyLong_CheckExact(op) && IS_MEDIUM_VALUE(op)) { _PyFreeList_Free(&_Py_small_object_freelist, op); } - else { + else +#endif + { Py_TYPE(op)->tp_free((PyObject *)op); } } diff --git a/Objects/object.c b/Objects/object.c index 25f5a2133d5745c..6b2409f89824350 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -2079,7 +2079,6 @@ void _PyObject_DebugTypeStats(FILE *out) { _PyDict_DebugMallocStats(out); - _PyFloat_DebugMallocStats(out); _PyFrame_DebugMallocStats(out); _PyList_DebugMallocStats(out); _PyTuple_DebugMallocStats(out); From 367710ec493c8afbf02b5b708f6f2ca4b98b457c Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Fri, 22 Oct 2021 17:23:29 +0100 Subject: [PATCH 3/7] Add NEWS. --- .../Core and Builtins/2021-10-22-17-23-15.bpo-45575._oOrzd.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2021-10-22-17-23-15.bpo-45575._oOrzd.rst diff --git a/Misc/NEWS.d/next/Core and Builtins/2021-10-22-17-23-15.bpo-45575._oOrzd.rst b/Misc/NEWS.d/next/Core and Builtins/2021-10-22-17-23-15.bpo-45575._oOrzd.rst new file mode 100644 index 000000000000000..25fc0e777df9f62 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2021-10-22-17-23-15.bpo-45575._oOrzd.rst @@ -0,0 +1 @@ +Use a common free list for both ints and floats. From 441818445a634ddfc3f2f33b388b4080999be373 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Fri, 22 Oct 2021 17:46:18 +0100 Subject: [PATCH 4/7] Move stuff around to support address sanitizer. --- Include/internal/pycore_pymem.h | 4 +- Objects/obmalloc.c | 119 +++++++++++++++++--------------- 2 files changed, 65 insertions(+), 58 deletions(-) diff --git a/Include/internal/pycore_pymem.h b/Include/internal/pycore_pymem.h index 1999596c20145c5..0d7e0443342ecec 100644 --- a/Include/internal/pycore_pymem.h +++ b/Include/internal/pycore_pymem.h @@ -100,6 +100,7 @@ void *_PyObject_VirtualAlloc(size_t size); void _PyObject_VirtualFree(void *, size_t size); +#if WITH_FREELISTS /* Free lists. * * Free lists have a pointer to their first entry and @@ -117,8 +118,6 @@ typedef struct _freelist { uint16_t capacity; } _PyFreeList; -extern int _PyObject_BulkFree(void *ptr); - extern void *_PyFreeList_HalfFillAndAllocate(_PyFreeList *list); extern void _PyFreeList_FreeToFull(_PyFreeList *list, void *ptr); @@ -144,7 +143,6 @@ _PyFreeList_Free(_PyFreeList *list, void *ptr) { _PyFreeList_FreeToFull(list, ptr); } -#if WITH_FREELISTS extern _PyFreeList _Py_small_object_freelist; #endif diff --git a/Objects/obmalloc.c b/Objects/obmalloc.c index f60ac291358b242..cf63afe49a324d2 100644 --- a/Objects/obmalloc.c +++ b/Objects/obmalloc.c @@ -733,6 +733,70 @@ PyObject_Free(void *ptr) # define LIKELY(value) (value) #endif +#if WITH_FREELISTS + +#define SMALL_OBJECT_FREELIST_SIZE 256 + +union _small_object { + PyFloatObject f; + PyLongObject l; +}; + +_PyFreeList _Py_small_object_freelist = { + NULL, + SMALL_OBJECT_FREELIST_SIZE, + sizeof(union _small_object), + SMALL_OBJECT_FREELIST_SIZE +}; + +void * +_PyFreeList_HalfFillAndAllocate(_PyFreeList *list) +{ + assert(list->ptr == NULL); + if (list->space < 4) { + return PyObject_Malloc(list->size); + } + uint32_t i = 0; + for (; i < list->space>>1; i++) { + void* ptr = PyObject_Malloc(list->size); + if (ptr == NULL) { + break; + } + *((void**)ptr) = list->ptr; + list->ptr = ptr; + } + if (i == 0) { + return NULL; + } + list->space -= (i-1); + void *result = list->ptr; + list->ptr = *((void **)result); + return result; +} + +void +_PyFreeList_FreeToFull(_PyFreeList *list, void *ptr) +{ + assert(list->space == 0); + if (list->ptr == NULL) { + PyObject_Free(ptr); + return; + } + int space = 0; + void *head = list->ptr; + while (head) { + void *next = *((void**)head); + PyObject_Free(head); + head = next; + space++; + } + list->ptr = ptr; + *((void **)ptr) = NULL; + list->space = space-1; +} + +#endif + #ifdef WITH_PYMALLOC #ifdef WITH_VALGRIND @@ -1972,40 +2036,6 @@ pymalloc_alloc(void *ctx, size_t nbytes) return (void *)bp; } -union _small_object { - PyFloatObject f; - PyLongObject l; -}; - -#if WITH_FREELISTS -_PyFreeList _Py_small_object_freelist = { NULL, 256, sizeof(union _small_object), 256 }; -#endif - -void * -_PyFreeList_HalfFillAndAllocate(_PyFreeList *list) -{ - assert(list->ptr == NULL); - if (list->space < 4) { - return PyObject_Malloc(list->size); - } - uint32_t i = 0; - for (; i < list->space>>1; i++) { - void* ptr = PyObject_Malloc(list->size); - if (ptr == NULL) { - break; - } - *((void**)ptr) = list->ptr; - list->ptr = ptr; - } - if (i == 0) { - return NULL; - } - list->space -= (i-1); - void *result = list->ptr; - list->ptr = *((void **)result); - return result; -} - static void * _PyObject_Malloc(void *ctx, size_t nbytes) @@ -2302,27 +2332,6 @@ _PyObject_Free(void *ctx, void *p) } } -void -_PyFreeList_FreeToFull(_PyFreeList *list, void *ptr) -{ - assert(list->space == 0); - if (list->ptr == NULL) { - PyObject_Free(ptr); - return; - } - int space = 0; - void *head = list->ptr; - while (head) { - void *next = *((void**)head); - PyObject_Free(head); - head = next; - space++; - } - list->ptr = ptr; - *((void **)ptr) = NULL; - list->space = space-1; -} - /* pymalloc realloc. If nbytes==0, then as the Python docs promise, we do not treat this like From d1ed5f6d763e3d9dd8e92777b2fd1f88bd98818e Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Sun, 24 Oct 2021 13:56:34 +0100 Subject: [PATCH 5/7] Account for non-64bit platforms. --- Objects/longobject.c | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/Objects/longobject.c b/Objects/longobject.c index 04d522e869fbddf..f9a6e5b2e537580 100644 --- a/Objects/longobject.c +++ b/Objects/longobject.c @@ -113,6 +113,15 @@ long_normalize(PyLongObject *v) #define MAX_LONG_DIGITS \ ((PY_SSIZE_T_MAX - offsetof(PyLongObject, ob_digit))/sizeof(digit)) +union _small_object { + PyFloatObject f; + PyLongObject l; +}; + +/* Free list is shared between ints and floats as they take the same amount of memory (on most platforms) + */ +#define MAX_DIGITS_FOR_FREELIST ((sizeof(union _small_object) - offsetof(PyLongObject, ob_digit))/sizeof(digit)) + PyLongObject * _PyLong_New(Py_ssize_t size) { @@ -122,16 +131,23 @@ _PyLong_New(Py_ssize_t size) "too many digits in integer"); return NULL; } - /* Fast operations for single digit integers (including zero) - * assume that there is always at least one digit present. */ - Py_ssize_t ndigits = size ? size : 1; - /* Number of bytes needed is: offsetof(PyLongObject, ob_digit) + - sizeof(digit)*size. Previous incarnations of this code used - sizeof(PyVarObject) instead of the offsetof, but this risks being - incorrect in the presence of padding between the PyVarObject header - and the digits. */ - result = PyObject_Malloc(offsetof(PyLongObject, ob_digit) + - ndigits*sizeof(digit)); +#if WITH_FREELISTS + if (size <= MAX_DIGITS_FOR_FREELIST) { + result = (PyLongObject *)_PyFreeList_Alloc(&_Py_small_object_freelist); +#else + if (size == 0) { + result = PyObject_Malloc(sizeof(PyLongObject)); +#endif + } + else { + /* Number of bytes needed is: offsetof(PyLongObject, ob_digit) + + sizeof(digit)*size. Previous incarnations of this code used + sizeof(PyVarObject) instead of the offsetof, but this risks being + incorrect in the presence of padding between the PyVarObject header + and the digits. */ + result = PyObject_Malloc(offsetof(PyLongObject, ob_digit) + + size*sizeof(digit)); + } if (!result) { PyErr_NoMemory(); return NULL; From 69a378c61a1129f8fc74e33ba2959bc15877e2af Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Mon, 25 Oct 2021 12:49:51 +0100 Subject: [PATCH 6/7] Make small object freelists per-interpreter. --- Include/internal/pycore_interp.h | 22 +++++----------------- Include/internal/pycore_pymem.h | 2 +- Objects/floatobject.c | 18 ++++++------------ Objects/longobject.c | 11 +++++++---- Objects/obmalloc.c | 29 +++++++++++++++-------------- Python/pystate.c | 14 ++++++++++++++ 6 files changed, 48 insertions(+), 48 deletions(-) diff --git a/Include/internal/pycore_interp.h b/Include/internal/pycore_interp.h index 7cba467ab431a05..f1e116f51eaaf7d 100644 --- a/Include/internal/pycore_interp.h +++ b/Include/internal/pycore_interp.h @@ -12,6 +12,7 @@ extern "C" { #include "pycore_ast_state.h" // struct ast_state #include "pycore_gil.h" // struct _gil_runtime_state #include "pycore_gc.h" // struct _gc_runtime_state +#include "pycore_pymem.h" // free lists #include "pycore_warnings.h" // struct _warnings_runtime_state struct _pending_calls { @@ -88,7 +89,6 @@ struct _Py_unicode_state { #ifndef WITH_FREELISTS # define WITH_FREELISTS 0 // without freelists -# define PyFloat_MAXFREELIST 0 // for tuples only store empty tuple singleton # define PyTuple_MAXSAVESIZE 1 # define PyTuple_MAXFREELIST 1 @@ -99,20 +99,6 @@ struct _Py_unicode_state { # 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 */ #ifndef PyTuple_MAXSAVESIZE // Largest tuple to save on free list @@ -272,6 +258,10 @@ struct _is { int finalizing; +#if WITH_FREELISTS + _PyFreeList small_object_freelist; +#endif + struct _ceval_state ceval; struct _gc_runtime_state gc; @@ -337,7 +327,6 @@ struct _is { PyLongObject* small_ints[_PY_NSMALLNEGINTS + _PY_NSMALLPOSINTS]; struct _Py_bytes_state bytes; struct _Py_unicode_state unicode; - struct _Py_float_state float_state; /* Using a cache is very effective since typically only a single slice is created and then deleted again. */ PySliceObject *slice_cache; @@ -349,7 +338,6 @@ struct _is { struct _Py_async_gen_state async_gen; struct _Py_context_state context; struct _Py_exc_state exc_state; - struct ast_state ast; struct type_cache type_cache; }; diff --git a/Include/internal/pycore_pymem.h b/Include/internal/pycore_pymem.h index 0d7e0443342ecec..c439e57edc75b93 100644 --- a/Include/internal/pycore_pymem.h +++ b/Include/internal/pycore_pymem.h @@ -143,7 +143,7 @@ _PyFreeList_Free(_PyFreeList *list, void *ptr) { _PyFreeList_FreeToFull(list, ptr); } -extern _PyFreeList _Py_small_object_freelist; +extern void _PyFreeList_Clear(_PyFreeList *list); #endif #ifdef __cplusplus diff --git a/Objects/floatobject.c b/Objects/floatobject.c index bc0b9c576d827ec..73f04467139d475 100644 --- a/Objects/floatobject.c +++ b/Objects/floatobject.c @@ -115,7 +115,8 @@ PyObject * PyFloat_FromDouble(double fval) { #if WITH_FREELISTS - PyFloatObject *op = (PyFloatObject *)_PyFreeList_Alloc(&_Py_small_object_freelist); + PyInterpreterState *interp = _PyInterpreterState_GET(); + PyFloatObject *op = (PyFloatObject *)_PyFreeList_Alloc(&interp->small_object_freelist); #else PyFloatObject *op = (PyFloatObject *)PyObject_Malloc(sizeof(PyFloatObject)); #endif @@ -215,7 +216,8 @@ float_dealloc(PyFloatObject *op) { #if WITH_FREELISTS if (PyFloat_CheckExact(op)) { - _PyFreeList_Free(&_Py_small_object_freelist, op); + PyInterpreterState *interp = _PyInterpreterState_GET(); + _PyFreeList_Free(&interp->small_object_freelist, op); } else #endif @@ -2008,16 +2010,8 @@ _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) { - PyFloatObject *next = (PyFloatObject*) Py_TYPE(f); - PyObject_Free(f); - f = next; - } - state->free_list = NULL; - state->numfree = 0; +#if WITH_FREELISTS + _PyFreeList_Clear(&interp->small_object_freelist); #endif } diff --git a/Objects/longobject.c b/Objects/longobject.c index f9a6e5b2e537580..bf5ea4f58e04d8d 100644 --- a/Objects/longobject.c +++ b/Objects/longobject.c @@ -132,8 +132,9 @@ _PyLong_New(Py_ssize_t size) return NULL; } #if WITH_FREELISTS - if (size <= MAX_DIGITS_FOR_FREELIST) { - result = (PyLongObject *)_PyFreeList_Alloc(&_Py_small_object_freelist); + if (((size_t)size) <= MAX_DIGITS_FOR_FREELIST) { + PyInterpreterState *interp = _PyInterpreterState_GET(); + result = (PyLongObject *)_PyFreeList_Alloc(&interp->small_object_freelist); #else if (size == 0) { result = PyObject_Malloc(sizeof(PyLongObject)); @@ -188,7 +189,8 @@ _PyLong_FromMedium(sdigit x) assert(!IS_SMALL_INT(x)); assert(is_medium_int(x)); #if WITH_FREELISTS - PyLongObject *v = (PyLongObject *)_PyFreeList_Alloc(&_Py_small_object_freelist); + PyInterpreterState *interp = _PyInterpreterState_GET(); + PyLongObject *v = (PyLongObject *)_PyFreeList_Alloc(&interp->small_object_freelist); #else PyLongObject *v = (PyLongObject *)PyObject_Malloc(sizeof(PyLongObject)); #endif @@ -5767,7 +5769,8 @@ int_dealloc(PyLongObject *op) { #if WITH_FREELISTS if (PyLong_CheckExact(op) && IS_MEDIUM_VALUE(op)) { - _PyFreeList_Free(&_Py_small_object_freelist, op); + PyInterpreterState *interp = _PyInterpreterState_GET(); + _PyFreeList_Free(&interp->small_object_freelist, op); } else #endif diff --git a/Objects/obmalloc.c b/Objects/obmalloc.c index cf63afe49a324d2..9f1d5e0892271bd 100644 --- a/Objects/obmalloc.c +++ b/Objects/obmalloc.c @@ -735,20 +735,6 @@ PyObject_Free(void *ptr) #if WITH_FREELISTS -#define SMALL_OBJECT_FREELIST_SIZE 256 - -union _small_object { - PyFloatObject f; - PyLongObject l; -}; - -_PyFreeList _Py_small_object_freelist = { - NULL, - SMALL_OBJECT_FREELIST_SIZE, - sizeof(union _small_object), - SMALL_OBJECT_FREELIST_SIZE -}; - void * _PyFreeList_HalfFillAndAllocate(_PyFreeList *list) { @@ -774,6 +760,21 @@ _PyFreeList_HalfFillAndAllocate(_PyFreeList *list) return result; } +void +_PyFreeList_Clear(_PyFreeList *list) +{ + int space = 0; + void *head = list->ptr; + while (head) { + void *next = *((void**)head); + PyObject_Free(head); + head = next; + space++; + } + list->ptr = NULL; + list->space += space; +} + void _PyFreeList_FreeToFull(_PyFreeList *list, void *ptr) { diff --git a/Python/pystate.c b/Python/pystate.c index 7804e17a064e154..0382f9ab8944c2d 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -201,6 +201,13 @@ _PyInterpreterState_Enable(_PyRuntimeState *runtime) return _PyStatus_OK(); } +/* For small object freelist */ +#define SMALL_OBJECT_FREELIST_SIZE 256 +union _small_object { + PyFloatObject f; + PyLongObject l; +}; + PyInterpreterState * PyInterpreterState_New(void) { @@ -270,6 +277,13 @@ PyInterpreterState_New(void) interp->audit_hooks = NULL; +#if WITH_FREELISTS + interp->small_object_freelist.ptr = NULL; + interp->small_object_freelist.space = SMALL_OBJECT_FREELIST_SIZE; + interp->small_object_freelist.size = sizeof(union _small_object); + interp->small_object_freelist.capacity = SMALL_OBJECT_FREELIST_SIZE; +#endif + return interp; out_of_memory: From 0e9c2d0595d3603d355536d93a771edcc551db5c Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Mon, 25 Oct 2021 13:49:27 +0100 Subject: [PATCH 7/7] Fix typo. --- Include/internal/pycore_pymem.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Include/internal/pycore_pymem.h b/Include/internal/pycore_pymem.h index c439e57edc75b93..e19077be2906f52 100644 --- a/Include/internal/pycore_pymem.h +++ b/Include/internal/pycore_pymem.h @@ -104,7 +104,7 @@ void _PyObject_VirtualFree(void *, size_t size); /* Free lists. * * Free lists have a pointer to their first entry and - * the amunt of space available allowing fast checks + * the amount of space available allowing fast checks * for emptiness and fullness. * When empty they are half filled and when full they are * completely emptied. This helps the underlying allocator