Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bpo-45522: Allow to disable freelists on build time #29056

Merged
merged 5 commits into from
Oct 21, 2021
Merged
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
5 changes: 5 additions & 0 deletions Doc/whatsnew/3.11.rst
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,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
=============

Expand Down
37 changes: 37 additions & 0 deletions Include/internal/pycore_interp.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -119,33 +138,44 @@ struct _Py_tuple_state {
#endif

struct _Py_list_state {
#if PyList_MAXFREELIST > 0
PyListObject *free_list[PyList_MAXFREELIST];
int numfree;
#endif
};

#ifndef PyDict_MAXFREELIST
# define PyDict_MAXFREELIST 80
#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
# define _PyAsyncGen_MAXFREELIST 80
#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
Expand All @@ -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 {
Expand Down
13 changes: 12 additions & 1 deletion Lib/test/test_sys.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
The internal freelists for frame, float, list, dict, async generators, and
context objects can now be disabled.
27 changes: 23 additions & 4 deletions Objects/dictobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand All @@ -260,14 +263,15 @@ _PyDict_ClearFreeList(PyInterpreterState *interp)
while (state->keys_numfree) {
PyObject_Free(state->keys_free_list[--state->keys_numfree]);
}
#endif
}


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;
Expand All @@ -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)
Expand Down Expand Up @@ -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()
Expand All @@ -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<<log2_size)
Expand Down Expand Up @@ -611,6 +619,7 @@ free_keys_object(PyDictKeysObject *keys)
Py_XDECREF(entries[i].me_key);
Py_XDECREF(entries[i].me_value);
}
#if PyDict_MAXFREELIST > 0
struct _Py_dict_state *state = get_dict_state();
#ifdef Py_DEBUG
// free_keys_object() must not be called after _PyDict_Fini()
Expand All @@ -620,6 +629,7 @@ free_keys_object(PyDictKeysObject *keys)
state->keys_free_list[state->keys_numfree++] = keys;
return;
}
#endif
PyObject_Free(keys);
}

Expand All @@ -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()
Expand All @@ -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);
Expand Down Expand Up @@ -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()
Expand All @@ -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);
}
}
Expand Down Expand Up @@ -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()
Expand All @@ -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
Expand Down
21 changes: 17 additions & 4 deletions Objects/floatobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()
Expand All @@ -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();
Expand Down Expand Up @@ -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
Expand All @@ -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);
}
}
Expand Down Expand Up @@ -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) {
Expand All @@ -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
Expand All @@ -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
}


Expand Down
Loading