]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-111968: Use per-thread freelists for dict in free-threading (gh-114323)
authorDonghee Na <donghee.na@python.org>
Thu, 1 Feb 2024 20:53:53 +0000 (05:53 +0900)
committerGitHub <noreply@github.com>
Thu, 1 Feb 2024 20:53:53 +0000 (20:53 +0000)
13 files changed:
Include/internal/pycore_dict.h
Include/internal/pycore_dict_state.h
Include/internal/pycore_freelist.h
Include/internal/pycore_gc.h
Include/internal/pycore_interp.h
Objects/dictobject.c
Objects/floatobject.c
Objects/genobject.c
Objects/listobject.c
Python/context.c
Python/gc_free_threading.c
Python/gc_gil.c
Python/pystate.c

index b4e1f8cf1e320b3c10f26ad8d846cb59a78beb41..60acd89cf6c34a68a48ac7cc87bd2452d48f58cc 100644 (file)
@@ -9,6 +9,7 @@ extern "C" {
 #  error "this header requires Py_BUILD_CORE define"
 #endif
 
+#include "pycore_freelist.h"      // _PyFreeListState
 #include "pycore_identifier.h"    // _Py_Identifier
 #include "pycore_object.h"        // PyDictOrValues
 
@@ -69,7 +70,7 @@ extern PyObject* _PyDictView_Intersect(PyObject* self, PyObject *other);
 
 /* runtime lifecycle */
 
-extern void _PyDict_Fini(PyInterpreterState *interp);
+extern void _PyDict_Fini(PyInterpreterState *state);
 
 
 /* other API */
index ece0f10ca251707a0feaca998f8a6fa131cf9bd3..a6dd63d36e040e64a12b6b9e865826b380f4e91a 100644 (file)
@@ -8,16 +8,6 @@ extern "C" {
 #  error "this header requires Py_BUILD_CORE define"
 #endif
 
-
-#ifndef WITH_FREELISTS
-// without freelists
-#  define PyDict_MAXFREELIST 0
-#endif
-
-#ifndef PyDict_MAXFREELIST
-#  define PyDict_MAXFREELIST 80
-#endif
-
 #define DICT_MAX_WATCHERS 8
 
 struct _Py_dict_state {
@@ -26,15 +16,6 @@ struct _Py_dict_state {
      * time that a dictionary is modified. */
     uint64_t global_version;
     uint32_t next_keys_version;
-
-#if PyDict_MAXFREELIST > 0
-    /* Dictionary reuse scheme to save calls to malloc and free */
-    PyDictObject *free_list[PyDict_MAXFREELIST];
-    PyDictKeysObject *keys_free_list[PyDict_MAXFREELIST];
-    int numfree;
-    int keys_numfree;
-#endif
-
     PyDict_WatchCallback watchers[DICT_MAX_WATCHERS];
 };
 
index b91d2bc066b783b546133a816efcc00d76466a20..82a42300991eccda0e478a8310e69019793262c4 100644 (file)
@@ -17,6 +17,7 @@ extern "C" {
 #  define PyTuple_NFREELISTS PyTuple_MAXSAVESIZE
 #  define PyTuple_MAXFREELIST 2000
 #  define PyList_MAXFREELIST 80
+#  define PyDict_MAXFREELIST 80
 #  define PyFloat_MAXFREELIST 100
 #  define PyContext_MAXFREELIST 255
 # define _PyAsyncGen_MAXFREELIST 80
@@ -25,6 +26,7 @@ extern "C" {
 #  define PyTuple_NFREELISTS 0
 #  define PyTuple_MAXFREELIST 0
 #  define PyList_MAXFREELIST 0
+#  define PyDict_MAXFREELIST 0
 #  define PyFloat_MAXFREELIST 0
 #  define PyContext_MAXFREELIST 0
 #  define _PyAsyncGen_MAXFREELIST 0
@@ -65,6 +67,16 @@ struct _Py_float_state {
 #endif
 };
 
+struct _Py_dict_freelist {
+#ifdef WITH_FREELISTS
+    /* Dictionary reuse scheme to save calls to malloc and free */
+    PyDictObject *free_list[PyDict_MAXFREELIST];
+    PyDictKeysObject *keys_free_list[PyDict_MAXFREELIST];
+    int numfree;
+    int keys_numfree;
+#endif
+};
+
 struct _Py_slice_state {
 #ifdef WITH_FREELISTS
     /* Using a cache is very effective since typically only a single slice is
@@ -106,6 +118,7 @@ typedef struct _Py_freelist_state {
     struct _Py_float_state floats;
     struct _Py_tuple_state tuples;
     struct _Py_list_state lists;
+    struct _Py_dict_freelist dicts;
     struct _Py_slice_state slices;
     struct _Py_context_state contexts;
     struct _Py_async_gen_state async_gens;
index b362a294a59042ae9345e71b543c59be6831b36a..ca1d9fdf5253b8dc99c1f96da01f239368a9237f 100644 (file)
@@ -267,7 +267,7 @@ extern void _PyTuple_ClearFreeList(_PyFreeListState *state, int is_finalization)
 extern void _PyFloat_ClearFreeList(_PyFreeListState *state, int is_finalization);
 extern void _PyList_ClearFreeList(_PyFreeListState *state, int is_finalization);
 extern void _PySlice_ClearCache(_PyFreeListState *state);
-extern void _PyDict_ClearFreeList(PyInterpreterState *interp);
+extern void _PyDict_ClearFreeList(_PyFreeListState *state, int is_finalization);
 extern void _PyAsyncGen_ClearFreeLists(_PyFreeListState *state, int is_finalization);
 extern void _PyContext_ClearFreeList(_PyFreeListState *state, int is_finalization);
 extern void _Py_ScheduleGC(PyInterpreterState *interp);
index 04e75940dcb5734d881a25035e56c0bf60dd319c..c4732b1534199b40a169f5205040ba3751b011c6 100644 (file)
@@ -20,6 +20,7 @@ extern "C" {
 #include "pycore_dtoa.h"          // struct _dtoa_state
 #include "pycore_exceptions.h"    // struct _Py_exc_state
 #include "pycore_floatobject.h"   // struct _Py_float_state
+#include "pycore_freelist.h"      // struct _Py_freelist_state
 #include "pycore_function.h"      // FUNC_MAX_WATCHERS
 #include "pycore_gc.h"            // struct _gc_runtime_state
 #include "pycore_genobject.h"     // struct _Py_async_gen_state
@@ -230,7 +231,6 @@ struct _is {
     struct _dtoa_state dtoa;
     struct _py_func_state func_state;
 
-    struct _Py_tuple_state tuple;
     struct _Py_dict_state dict_state;
     struct _Py_exc_state exc_state;
 
index 23d7e9b5e38a35629e0cbf3e21d0daac7bdba18c..e24887b7d781bb516a3f9ae452f8dbbe9c0ff224 100644 (file)
@@ -118,6 +118,7 @@ As a consequence of this, split keys have a maximum size of 16.
 #include "pycore_ceval.h"         // _PyEval_GetBuiltin()
 #include "pycore_code.h"          // stats
 #include "pycore_dict.h"          // export _PyDict_SizeOf()
+#include "pycore_freelist.h"      // _PyFreeListState_GET()
 #include "pycore_gc.h"            // _PyObject_GC_IS_TRACKED()
 #include "pycore_object.h"        // _PyObject_GC_TRACK(), _PyDebugAllocatorStats()
 #include "pycore_pyerrors.h"      // _PyErr_GetRaisedException()
@@ -242,40 +243,44 @@ static PyObject* dict_iter(PyObject *dict);
 #include "clinic/dictobject.c.h"
 
 
-#if PyDict_MAXFREELIST > 0
-static struct _Py_dict_state *
-get_dict_state(PyInterpreterState *interp)
+#ifdef WITH_FREELISTS
+static struct _Py_dict_freelist *
+get_dict_state(void)
 {
-    return &interp->dict_state;
+    _PyFreeListState *state = _PyFreeListState_GET();
+    return &state->dicts;
 }
 #endif
 
 
 void
-_PyDict_ClearFreeList(PyInterpreterState *interp)
+_PyDict_ClearFreeList(_PyFreeListState *freelist_state, int is_finalization)
 {
-#if PyDict_MAXFREELIST > 0
-    struct _Py_dict_state *state = &interp->dict_state;
-    while (state->numfree) {
+#ifdef WITH_FREELISTS
+    struct _Py_dict_freelist *state = &freelist_state->dicts;
+    while (state->numfree > 0) {
         PyDictObject *op = state->free_list[--state->numfree];
         assert(PyDict_CheckExact(op));
         PyObject_GC_Del(op);
     }
-    while (state->keys_numfree) {
+    while (state->keys_numfree > 0) {
         PyMem_Free(state->keys_free_list[--state->keys_numfree]);
     }
+    if (is_finalization) {
+        state->numfree = -1;
+        state->keys_numfree = -1;
+    }
 #endif
 }
 
-
 void
-_PyDict_Fini(PyInterpreterState *interp)
+_PyDict_Fini(PyInterpreterState *Py_UNUSED(interp))
 {
-    _PyDict_ClearFreeList(interp);
-#if defined(Py_DEBUG) && PyDict_MAXFREELIST > 0
-    struct _Py_dict_state *state = &interp->dict_state;
-    state->numfree = -1;
-    state->keys_numfree = -1;
+    // With Py_GIL_DISABLED:
+    // the freelists for the current thread state have already been cleared.
+#ifndef Py_GIL_DISABLED
+     _PyFreeListState *state = _PyFreeListState_GET();
+    _PyDict_ClearFreeList(state, 1);
 #endif
 }
 
@@ -290,9 +295,8 @@ unicode_get_hash(PyObject *o)
 void
 _PyDict_DebugMallocStats(FILE *out)
 {
-#if PyDict_MAXFREELIST > 0
-    PyInterpreterState *interp = _PyInterpreterState_GET();
-    struct _Py_dict_state *state = get_dict_state(interp);
+#ifdef WITH_FREELISTS
+    struct _Py_dict_freelist *state = get_dict_state();
     _PyDebugAllocatorStats(out, "free PyDictObject",
                            state->numfree, sizeof(PyDictObject));
 #endif
@@ -300,7 +304,7 @@ _PyDict_DebugMallocStats(FILE *out)
 
 #define DK_MASK(dk) (DK_SIZE(dk)-1)
 
-static void free_keys_object(PyInterpreterState *interp, PyDictKeysObject *keys);
+static void free_keys_object(PyDictKeysObject *keys);
 
 /* PyDictKeysObject has refcounts like PyObject does, so we have the
    following two functions to mirror what Py_INCREF() and Py_DECREF() do.
@@ -348,7 +352,7 @@ dictkeys_decref(PyInterpreterState *interp, PyDictKeysObject *dk)
                 Py_XDECREF(entries[i].me_value);
             }
         }
-        free_keys_object(interp, dk);
+        free_keys_object(dk);
     }
 }
 
@@ -643,12 +647,8 @@ new_keys_object(PyInterpreterState *interp, uint8_t log2_size, bool unicode)
         log2_bytes = log2_size + 2;
     }
 
-#if PyDict_MAXFREELIST > 0
-    struct _Py_dict_state *state = get_dict_state(interp);
-#ifdef Py_DEBUG
-    // new_keys_object() must not be called after _PyDict_Fini()
-    assert(state->keys_numfree != -1);
-#endif
+#ifdef WITH_FREELISTS
+    struct _Py_dict_freelist *state = get_dict_state();
     if (log2_size == PyDict_LOG_MINSIZE && unicode && state->keys_numfree > 0) {
         dk = state->keys_free_list[--state->keys_numfree];
         OBJECT_STAT_INC(from_freelist);
@@ -680,16 +680,13 @@ new_keys_object(PyInterpreterState *interp, uint8_t log2_size, bool unicode)
 }
 
 static void
-free_keys_object(PyInterpreterState *interp, PyDictKeysObject *keys)
+free_keys_object(PyDictKeysObject *keys)
 {
-#if PyDict_MAXFREELIST > 0
-    struct _Py_dict_state *state = get_dict_state(interp);
-#ifdef Py_DEBUG
-    // free_keys_object() must not be called after _PyDict_Fini()
-    assert(state->keys_numfree != -1);
-#endif
+#ifdef WITH_FREELISTS
+    struct _Py_dict_freelist *state = get_dict_state();
     if (DK_LOG_SIZE(keys) == PyDict_LOG_MINSIZE
             && state->keys_numfree < PyDict_MAXFREELIST
+            && state->keys_numfree >= 0
             && DK_IS_UNICODE(keys)) {
         state->keys_free_list[state->keys_numfree++] = keys;
         OBJECT_STAT_INC(to_freelist);
@@ -730,13 +727,9 @@ new_dict(PyInterpreterState *interp,
 {
     PyDictObject *mp;
     assert(keys != NULL);
-#if PyDict_MAXFREELIST > 0
-    struct _Py_dict_state *state = get_dict_state(interp);
-#ifdef Py_DEBUG
-    // new_dict() must not be called after _PyDict_Fini()
-    assert(state->numfree != -1);
-#endif
-    if (state->numfree) {
+#ifdef WITH_FREELISTS
+    struct _Py_dict_freelist *state = get_dict_state();
+    if (state->numfree > 0) {
         mp = state->free_list[--state->numfree];
         assert (mp != NULL);
         assert (Py_IS_TYPE(mp, &PyDict_Type));
@@ -1547,7 +1540,7 @@ dictresize(PyInterpreterState *interp, PyDictObject *mp,
 #endif
             assert(oldkeys->dk_kind != DICT_KEYS_SPLIT);
             assert(oldkeys->dk_refcnt == 1);
-            free_keys_object(interp, oldkeys);
+            free_keys_object(oldkeys);
         }
     }
 
@@ -2458,13 +2451,10 @@ dict_dealloc(PyObject *self)
         assert(keys->dk_refcnt == 1 || keys == Py_EMPTY_KEYS);
         dictkeys_decref(interp, keys);
     }
-#if PyDict_MAXFREELIST > 0
-    struct _Py_dict_state *state = get_dict_state(interp);
-#ifdef Py_DEBUG
-    // new_dict() must not be called after _PyDict_Fini()
-    assert(state->numfree != -1);
-#endif
-    if (state->numfree < PyDict_MAXFREELIST && Py_IS_TYPE(mp, &PyDict_Type)) {
+#ifdef WITH_FREELISTS
+    struct _Py_dict_freelist *state = get_dict_state();
+    if (state->numfree < PyDict_MAXFREELIST && state->numfree >=0 &&
+        Py_IS_TYPE(mp, &PyDict_Type)) {
         state->free_list[state->numfree++] = mp;
         OBJECT_STAT_INC(to_freelist);
     }
index b7611d5f96ac3be4ef1e13aaab9fb12e36423e43..c440e0dab0e79faae0361d507ce62d4bfd175a91 100644 (file)
@@ -2013,7 +2013,11 @@ _PyFloat_ClearFreeList(_PyFreeListState *freelist_state, int is_finalization)
 void
 _PyFloat_Fini(_PyFreeListState *state)
 {
+    // With Py_GIL_DISABLED:
+    // the freelists for the current thread state have already been cleared.
+#ifndef Py_GIL_DISABLED
     _PyFloat_ClearFreeList(state, 1);
+#endif
 }
 
 void
index f47197330fdd8018d33ee6ca74882f08070b217b..ab523e46cceaa317a75702bd0fec48b0748ab12d 100644 (file)
@@ -1685,7 +1685,11 @@ _PyAsyncGen_ClearFreeLists(_PyFreeListState *freelist_state, int is_finalization
 void
 _PyAsyncGen_Fini(_PyFreeListState *state)
 {
+    // With Py_GIL_DISABLED:
+    // the freelists for the current thread state have already been cleared.
+#ifndef Py_GIL_DISABLED
     _PyAsyncGen_ClearFreeLists(state, 1);
+#endif
 }
 
 
index 80a1f1da55b8bc874510c806822dc8323bb8f59e..da2b9cc32697ddaf3bb5d71356d220558a38ef12 100644 (file)
@@ -138,7 +138,11 @@ _PyList_ClearFreeList(_PyFreeListState *freelist_state, int is_finalization)
 void
 _PyList_Fini(_PyFreeListState *state)
 {
+    // With Py_GIL_DISABLED:
+    // the freelists for the current thread state have already been cleared.
+#ifndef Py_GIL_DISABLED
     _PyList_ClearFreeList(state, 1);
+#endif
 }
 
 /* Print summary info about the state of the optimized allocator */
index 294485e5b407dfeadc2cb53821fbbf475af431f8..793dfa2b72c7e3f2dcb5b938778dfdef9317c20e 100644 (file)
@@ -1287,7 +1287,11 @@ _PyContext_ClearFreeList(_PyFreeListState *freelist_state, int is_finalization)
 void
 _PyContext_Fini(_PyFreeListState *state)
 {
+    // With Py_GIL_DISABLED:
+    // the freelists for the current thread state have already been cleared.
+#ifndef Py_GIL_DISABLED
     _PyContext_ClearFreeList(state, 1);
+#endif
 }
 
 
index 53f927bfa65310427129ba2a542ece28cd1c4d8d..8fbcdb15109b76c747a6733166cf0957de7f465b 100644 (file)
@@ -1676,8 +1676,6 @@ PyUnstable_GC_VisitObjects(gcvisitobjects_t callback, void *arg)
 void
 _PyGC_ClearAllFreeLists(PyInterpreterState *interp)
 {
-    _PyDict_ClearFreeList(interp);
-
     HEAD_LOCK(&_PyRuntime);
     _PyThreadStateImpl *tstate = (_PyThreadStateImpl *)interp->threads.head;
     while (tstate != NULL) {
index 04c1c184250c60931f34d849034cc65d632e2fd8..4e2aa8f7af746c2018a68f5c2c667648db463d3c 100644 (file)
@@ -11,8 +11,6 @@
 void
 _PyGC_ClearAllFreeLists(PyInterpreterState *interp)
 {
-    _PyDict_ClearFreeList(interp);
-
     _Py_ClearFreeLists(&interp->freelist_state, 0);
 }
 
index 430121a6a35d7f9db32102870309a3edaccbfd48..27b6d0573ade3b3d9a9a3e638908e12e8c701381 100644 (file)
@@ -1461,9 +1461,12 @@ clear_datastack(PyThreadState *tstate)
 void
 _Py_ClearFreeLists(_PyFreeListState *state, int is_finalization)
 {
+    // In the free-threaded build, freelists are per-PyThreadState and cleared in PyThreadState_Clear()
+    // In the default build, freelists are per-interpreter and cleared in finalize_interp_types()
     _PyFloat_ClearFreeList(state, is_finalization);
     _PyTuple_ClearFreeList(state, is_finalization);
     _PyList_ClearFreeList(state, is_finalization);
+    _PyDict_ClearFreeList(state, is_finalization);
     _PyContext_ClearFreeList(state, is_finalization);
     _PyAsyncGen_ClearFreeLists(state, is_finalization);
     _PyObjectStackChunk_ClearFreeList(state, is_finalization);