GH-100240: Generic freelist, applied to ints by iritkatriel · Pull Request #101453 · python/cpython · GitHub
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions Include/internal/pycore_interp.h
2 changes: 2 additions & 0 deletions Include/internal/pycore_long.h
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,8 @@ _PyLong_IsPositiveSingleDigit(PyObject* sub) {
return ((size_t)signed_size) <= 1;
}

void _PyLong_Free(PyLongObject *op);

#ifdef __cplusplus
}
#endif
Expand Down
74 changes: 74 additions & 0 deletions Include/internal/pycore_pymem.h
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,80 @@ PyAPI_FUNC(int) _PyMem_GetAllocatorName(
PyAPI_FUNC(int) _PyMem_SetupAllocators(PyMemAllocatorName allocator);


/* Free lists.
*
* Free lists have a pointer to their first entry and
* 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
* avoid fragmentation and helps performance.
*/

typedef struct _freelist {
void *ptr;
uint32_t space;
uint16_t size;
uint16_t capacity;
#ifdef Py_STATS
int size_class;
#endif
} _PyFreeList;

extern void *_PyFreeList_HalfFillAndAllocate(_PyFreeList *list);
extern void _PyFreeList_FreeToFull(_PyFreeList *list, void *ptr);
extern void _PyFreeList_Clear(_PyFreeList *list);
extern void _PyFreeList_Disable(_PyFreeList *list);

static inline void *
_PyFreeList_Alloc(_PyFreeList *list) {
#ifdef Py_STATS
if (_py_stats) _py_stats->freelist_stats[list->size_class].allocations++;
#endif
if (list->ptr != NULL) {
void *result = list->ptr;
list->ptr = *((void **)result);
list->space++;
return result;
}
#ifdef Py_STATS
if (_py_stats) _py_stats->freelist_stats[list->size_class].empty++;
#endif
return _PyFreeList_HalfFillAndAllocate(list);
}

static inline void
_PyFreeList_Free(_PyFreeList *list, void *ptr) {
#ifdef Py_STATS
if (_py_stats) _py_stats->freelist_stats[list->size_class].frees++;
#endif
if (list->space) {
*((void **)ptr) = list->ptr;
list->ptr = ptr;
list->space--;
return;
}
#ifdef Py_STATS
if (_py_stats) _py_stats->freelist_stats[list->size_class].full++;
#endif
_PyFreeList_FreeToFull(list, ptr);
Comment thread
iritkatriel marked this conversation as resolved.
}

static inline void
_PyFreeList_Init(_PyFreeList *list, int size_class, int size, int capacity)
{
list->ptr = NULL;
list->size = size;
#ifdef Py_STATS
list->size_class = size_class;
#endif
#if WITH_FREELISTS
list->space = list->capacity = capacity;
#else
_PyFreeList_Disable(list);
#endif
}

#ifdef __cplusplus
}
#endif
Expand Down
8 changes: 8 additions & 0 deletions Include/pystats.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,13 @@ typedef struct _call_stats {
uint64_t eval_calls[EVAL_CALL_KINDS];
} CallStats;

typedef struct _generic_freelist_stats {
uint64_t allocations;
uint64_t frees;
uint64_t empty;
uint64_t full;
} GenericFreelistStats;

typedef struct _object_stats {
uint64_t increfs;
uint64_t decrefs;
Expand Down Expand Up @@ -78,6 +85,7 @@ typedef struct _stats {
OpcodeStats opcode_stats[256];
CallStats call_stats;
ObjectStats object_stats;
GenericFreelistStats freelist_stats[30]; // INTERP_NUM_FREELISTS
} PyStats;


Expand Down
50 changes: 34 additions & 16 deletions Objects/longobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include "pycore_bitutils.h" // _Py_popcount32()
#include "pycore_initconfig.h" // _PyStatus_OK()
#include "pycore_long.h" // _Py_SmallInts
#include "pycore_pymem.h" // Free lists
#include "pycore_object.h" // _PyObject_InitVar()
#include "pycore_pystate.h" // _Py_IsMainInterpreter()
#include "pycore_runtime.h" // _PY_NSMALLPOSINTS
Expand Down Expand Up @@ -46,7 +47,7 @@ static inline void
_Py_DECREF_INT(PyLongObject *op)
{
assert(PyLong_CheckExact(op));
_Py_DECREF_SPECIALIZED((PyObject *)op, (destructor)PyObject_Free);
_Py_DECREF_SPECIALIZED((PyObject *)op, (destructor)_PyLong_Free);
}

static inline int
Expand Down Expand Up @@ -152,16 +153,20 @@ _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, long_value.ob_digit) +
ndigits*sizeof(digit));
assert(size >= 0);
if (size <= 1) {
PyInterpreterState *interp = _PyInterpreterState_GET();
result = (PyLongObject *)_PyInterpreterState_FreelistAlloc(interp, sizeof(PyLongObject));
}
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, long_value.ob_digit) +
size*sizeof(digit));
}
if (!result) {
PyErr_NoMemory();
return NULL;
Expand Down Expand Up @@ -201,11 +206,11 @@ _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));
PyInterpreterState *interp = _PyInterpreterState_GET();
PyLongObject *v = (PyLongObject *)_PyInterpreterState_FreelistAlloc(
interp, sizeof(PyLongObject));
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;
Expand Down Expand Up @@ -267,6 +272,19 @@ _PyLong_FromSTwoDigits(stwodigits x)
return _PyLong_FromLarge(x);
}

void
_PyLong_Free(PyLongObject *op)
{
if (PyLong_CheckExact(op) && IS_MEDIUM_VALUE(op)) {
PyInterpreterState *interp = _PyInterpreterState_GET();
_PyInterpreterState_FreelistFree(interp, (PyObject*)op, sizeof(PyLongObject));
}
else
{
Py_TYPE(op)->tp_free((PyObject *)op);
}
}

int
_PyLong_AssignValue(PyObject **target, Py_ssize_t value)
{
Expand Down Expand Up @@ -6286,7 +6304,7 @@ PyTypeObject PyLong_Type = {
"int", /* tp_name */
offsetof(PyLongObject, long_value.ob_digit), /* tp_basicsize */
sizeof(digit), /* tp_itemsize */
0, /* tp_dealloc */
(destructor)_PyLong_Free, /* tp_dealloc */
0, /* tp_vectorcall_offset */
0, /* tp_getattr */
0, /* tp_setattr */
Expand Down
58 changes: 58 additions & 0 deletions Objects/obmalloc.c
Loading