Skip to content

bpo-40521: Make frame free list per-interpreter #20638

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

Merged
merged 1 commit into from
Jun 4, 2020
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
2 changes: 1 addition & 1 deletion Include/internal/pycore_gc.h
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ PyAPI_FUNC(void) _PyGC_InitState(struct _gc_runtime_state *);


// Functions to clear types free lists
extern void _PyFrame_ClearFreeList(void);
extern void _PyFrame_ClearFreeList(PyThreadState *tstate);
extern void _PyTuple_ClearFreeList(PyThreadState *tstate);
extern void _PyFloat_ClearFreeList(PyThreadState *tstate);
extern void _PyList_ClearFreeList(void);
Expand Down
7 changes: 7 additions & 0 deletions Include/internal/pycore_interp.h
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,12 @@ struct _Py_float_state {
PyFloatObject *free_list;
};

struct _Py_frame_state {
PyFrameObject *free_list;
/* number of frames currently in free_list */
int numfree;
};


/* interpreter state */

Expand Down Expand Up @@ -187,6 +193,7 @@ struct _is {
#endif
struct _Py_tuple_state tuple;
struct _Py_float_state float_state;
struct _Py_frame_state frame;

/* Using a cache is very effective since typically only a single slice is
created and then deleted again. */
Expand Down
2 changes: 1 addition & 1 deletion Include/internal/pycore_pylifecycle.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ extern PyStatus _PyGC_Init(PyThreadState *tstate);

/* Various internal finalizers */

extern void _PyFrame_Fini(void);
extern void _PyFrame_Fini(PyThreadState *tstate);
extern void _PyDict_Fini(void);
extern void _PyTuple_Fini(PyThreadState *tstate);
extern void _PyList_Fini(void);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
The tuple free lists, the empty tuple singleton, the float free list, and the
slice cache are no longer shared by all interpreters: each interpreter now has
its own free lists and caches.
The tuple free lists, the empty tuple singleton, the float free list, the slice
cache, and the frame free list are no longer shared by all interpreters: each
interpreter now its has own free lists and caches.
2 changes: 1 addition & 1 deletion Modules/gcmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -1026,7 +1026,7 @@ static void
clear_freelists(void)
{
PyThreadState *tstate = _PyThreadState_GET();
_PyFrame_ClearFreeList();
_PyFrame_ClearFreeList(tstate);
_PyTuple_ClearFreeList(tstate);
_PyFloat_ClearFreeList(tstate);
_PyList_ClearFreeList();
Expand Down
86 changes: 37 additions & 49 deletions Objects/frameobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -561,36 +561,25 @@ static PyGetSetDef frame_getsetlist[] = {
/* max value for numfree */
#define PyFrame_MAXFREELIST 200

/* bpo-40521: frame free lists are shared by all interpreters. */
#ifdef EXPERIMENTAL_ISOLATED_SUBINTERPRETERS
# undef PyFrame_MAXFREELIST
# define PyFrame_MAXFREELIST 0
#endif

#if PyFrame_MAXFREELIST > 0
static PyFrameObject *free_list = NULL;
static int numfree = 0; /* number of frames currently in free_list */
#endif

static void _Py_HOT_FUNCTION
frame_dealloc(PyFrameObject *f)
{
PyObject **p, **valuestack;
PyCodeObject *co;

if (_PyObject_GC_IS_TRACKED(f))
if (_PyObject_GC_IS_TRACKED(f)) {
_PyObject_GC_UNTRACK(f);
}

Py_TRASHCAN_SAFE_BEGIN(f)
/* Kill all local variables */
valuestack = f->f_valuestack;
for (p = f->f_localsplus; p < valuestack; p++)
PyObject **valuestack = f->f_valuestack;
for (PyObject **p = f->f_localsplus; p < valuestack; p++) {
Py_CLEAR(*p);
}

/* Free stack */
if (f->f_stacktop != NULL) {
for (p = valuestack; p < f->f_stacktop; p++)
for (PyObject **p = valuestack; p < f->f_stacktop; p++) {
Py_XDECREF(*p);
}
}

Py_XDECREF(f->f_back);
Expand All @@ -599,19 +588,21 @@ frame_dealloc(PyFrameObject *f)
Py_CLEAR(f->f_locals);
Py_CLEAR(f->f_trace);

co = f->f_code;
PyCodeObject *co = f->f_code;
if (co->co_zombieframe == NULL) {
co->co_zombieframe = f;
}
#if PyFrame_MAXFREELIST > 0
else if (numfree < PyFrame_MAXFREELIST) {
++numfree;
f->f_back = free_list;
free_list = f;
}
#endif
else {
PyObject_GC_Del(f);
PyInterpreterState *interp = _PyInterpreterState_GET();
struct _Py_frame_state *state = &interp->frame;
if (state->numfree < PyFrame_MAXFREELIST) {
++state->numfree;
f->f_back = state->free_list;
state->free_list = f;
}
else {
PyObject_GC_Del(f);
}
}

Py_DECREF(co);
Expand Down Expand Up @@ -789,21 +780,20 @@ frame_alloc(PyCodeObject *code)
Py_ssize_t ncells = PyTuple_GET_SIZE(code->co_cellvars);
Py_ssize_t nfrees = PyTuple_GET_SIZE(code->co_freevars);
Py_ssize_t extras = code->co_stacksize + code->co_nlocals + ncells + nfrees;
#if PyFrame_MAXFREELIST > 0
if (free_list == NULL)
#endif
PyInterpreterState *interp = _PyInterpreterState_GET();
struct _Py_frame_state *state = &interp->frame;
if (state->free_list == NULL)
{
f = PyObject_GC_NewVar(PyFrameObject, &PyFrame_Type, extras);
if (f == NULL) {
return NULL;
}
}
#if PyFrame_MAXFREELIST > 0
else {
assert(numfree > 0);
--numfree;
f = free_list;
free_list = free_list->f_back;
assert(state->numfree > 0);
--state->numfree;
f = state->free_list;
state->free_list = state->free_list->f_back;
if (Py_SIZE(f) < extras) {
PyFrameObject *new_f = PyObject_GC_Resize(PyFrameObject, f, extras);
if (new_f == NULL) {
Expand All @@ -814,7 +804,6 @@ frame_alloc(PyCodeObject *code)
}
_Py_NewReference((PyObject *)f);
}
#endif

f->f_code = code;
extras = code->co_nlocals + ncells + nfrees;
Expand Down Expand Up @@ -1183,34 +1172,33 @@ PyFrame_LocalsToFast(PyFrameObject *f, int clear)

/* Clear out the free list */
void
_PyFrame_ClearFreeList(void)
_PyFrame_ClearFreeList(PyThreadState *tstate)
{
#if PyFrame_MAXFREELIST > 0
while (free_list != NULL) {
PyFrameObject *f = free_list;
free_list = free_list->f_back;
struct _Py_frame_state *state = &tstate->interp->frame;
while (state->free_list != NULL) {
PyFrameObject *f = state->free_list;
state->free_list = state->free_list->f_back;
PyObject_GC_Del(f);
--numfree;
--state->numfree;
}
assert(numfree == 0);
#endif
assert(state->numfree == 0);
}

void
_PyFrame_Fini(void)
_PyFrame_Fini(PyThreadState *tstate)
{
_PyFrame_ClearFreeList();
_PyFrame_ClearFreeList(tstate);
}

/* Print summary info about the state of the optimized allocator */
void
_PyFrame_DebugMallocStats(FILE *out)
{
#if PyFrame_MAXFREELIST > 0
PyInterpreterState *interp = _PyInterpreterState_GET();
struct _Py_frame_state *state = &interp->frame;
_PyDebugAllocatorStats(out,
"free PyFrameObject",
numfree, sizeof(PyFrameObject));
#endif
state->numfree, sizeof(PyFrameObject));
}


Expand Down
5 changes: 1 addition & 4 deletions Python/pylifecycle.c
Original file line number Diff line number Diff line change
Expand Up @@ -1249,10 +1249,7 @@ flush_std_files(void)
static void
finalize_interp_types(PyThreadState *tstate, int is_main_interp)
{
if (is_main_interp) {
/* Sundry finalizers */
_PyFrame_Fini();
}
_PyFrame_Fini(tstate);
_PyTuple_Fini(tstate);
if (is_main_interp) {
_PyList_Fini();
Expand Down