[Python-checkins] bpo-40521: Make frame free list per-interpreter (GH-20638)

Victor Stinner webhook-mailer at python.org
Thu Jun 4 19:39:29 EDT 2020


https://github.com/python/cpython/commit/3744ed2c9c0b3905947602fc375de49533790cb9
commit: 3744ed2c9c0b3905947602fc375de49533790cb9
branch: master
author: Victor Stinner <vstinner at python.org>
committer: GitHub <noreply at github.com>
date: 2020-06-05T01:39:24+02:00
summary:

bpo-40521: Make frame free list per-interpreter (GH-20638)

Each interpreter now has its own frame free list:

* Move frame free list into PyInterpreterState.
* Add _Py_frame_state structure.
* Add tstate parameter to _PyFrame_ClearFreeList()
  and _PyFrame_Fini().
* Remove "#if PyFrame_MAXFREELIST > 0".
* Remove "#ifdef EXPERIMENTAL_ISOLATED_SUBINTERPRETERS".

files:
M Include/internal/pycore_gc.h
M Include/internal/pycore_interp.h
M Include/internal/pycore_pylifecycle.h
M Misc/NEWS.d/next/Core and Builtins/2020-05-20-01-17-34.bpo-40521.wvAehI.rst
M Modules/gcmodule.c
M Objects/frameobject.c
M Python/pylifecycle.c

diff --git a/Include/internal/pycore_gc.h b/Include/internal/pycore_gc.h
index f90d80be16878..01265d3f985b9 100644
--- a/Include/internal/pycore_gc.h
+++ b/Include/internal/pycore_gc.h
@@ -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);
diff --git a/Include/internal/pycore_interp.h b/Include/internal/pycore_interp.h
index 70054efe7ec71..9b805f004eaa6 100644
--- a/Include/internal/pycore_interp.h
+++ b/Include/internal/pycore_interp.h
@@ -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 */
 
@@ -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. */
diff --git a/Include/internal/pycore_pylifecycle.h b/Include/internal/pycore_pylifecycle.h
index bba9bd9b2bdb2..06d2ac167d619 100644
--- a/Include/internal/pycore_pylifecycle.h
+++ b/Include/internal/pycore_pylifecycle.h
@@ -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);
diff --git a/Misc/NEWS.d/next/Core and Builtins/2020-05-20-01-17-34.bpo-40521.wvAehI.rst b/Misc/NEWS.d/next/Core and Builtins/2020-05-20-01-17-34.bpo-40521.wvAehI.rst
index 74c7a499bdef0..71a1064ba7d14 100644
--- a/Misc/NEWS.d/next/Core and Builtins/2020-05-20-01-17-34.bpo-40521.wvAehI.rst	
+++ b/Misc/NEWS.d/next/Core and Builtins/2020-05-20-01-17-34.bpo-40521.wvAehI.rst	
@@ -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.
diff --git a/Modules/gcmodule.c b/Modules/gcmodule.c
index 0bad0f8917f37..45dc89d08c1fb 100644
--- a/Modules/gcmodule.c
+++ b/Modules/gcmodule.c
@@ -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();
diff --git a/Objects/frameobject.c b/Objects/frameobject.c
index b6d073bd456d0..0fe9f2a6666b2 100644
--- a/Objects/frameobject.c
+++ b/Objects/frameobject.c
@@ -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);
@@ -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);
@@ -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) {
@@ -814,7 +804,6 @@ frame_alloc(PyCodeObject *code)
         }
         _Py_NewReference((PyObject *)f);
     }
-#endif
 
     f->f_code = code;
     extras = code->co_nlocals + ncells + nfrees;
@@ -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));
 }
 
 
diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c
index ee9d698d7d089..1dbdbfdf5a318 100644
--- a/Python/pylifecycle.c
+++ b/Python/pylifecycle.c
@@ -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();



More information about the Python-checkins mailing list