summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDino Viehland <dinoviehland@meta.com>2024-05-07 00:22:26 (GMT)
committerGitHub <noreply@github.com>2024-05-07 00:22:26 (GMT)
commitff6cbb2503a8fe3fceeadd889e34fc9a8f308ecd (patch)
treeed2ab05ad2ca3f3819babe399933ffcb85c93ad4
parent723d4d2fe8e77b398f0ccffcfa541149caaac6a1 (diff)
downloadcpython-ff6cbb2503a8fe3fceeadd889e34fc9a8f308ecd.zip
cpython-ff6cbb2503a8fe3fceeadd889e34fc9a8f308ecd.tar.gz
cpython-ff6cbb2503a8fe3fceeadd889e34fc9a8f308ecd.tar.bz2
gh-112075: use per-thread dict version pool (#118676)
use thread state set of dict versions
-rw-r--r--Include/cpython/pystate.h1
-rw-r--r--Include/internal/pycore_dict.h21
-rw-r--r--Lib/test/test_free_threading/test_dict.py36
-rw-r--r--Modules/_testcapi/dict.c14
-rw-r--r--Python/pystate.c1
5 files changed, 70 insertions, 3 deletions
diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h
index 0611e29..2df9ecd 100644
--- a/Include/cpython/pystate.h
+++ b/Include/cpython/pystate.h
@@ -188,6 +188,7 @@ struct _ts {
PyObject *previous_executor;
+ uint64_t dict_global_version;
};
#ifdef Py_DEBUG
diff --git a/Include/internal/pycore_dict.h b/Include/internal/pycore_dict.h
index cb7d4c3..8d8d374 100644
--- a/Include/internal/pycore_dict.h
+++ b/Include/internal/pycore_dict.h
@@ -221,8 +221,25 @@ static inline PyDictUnicodeEntry* DK_UNICODE_ENTRIES(PyDictKeysObject *dk) {
#define DICT_WATCHER_AND_MODIFICATION_MASK ((1 << (DICT_MAX_WATCHERS + DICT_WATCHED_MUTATION_BITS)) - 1)
#ifdef Py_GIL_DISABLED
-#define DICT_NEXT_VERSION(INTERP) \
- (_Py_atomic_add_uint64(&(INTERP)->dict_state.global_version, DICT_VERSION_INCREMENT) + DICT_VERSION_INCREMENT)
+
+#define THREAD_LOCAL_DICT_VERSION_COUNT 256
+#define THREAD_LOCAL_DICT_VERSION_BATCH THREAD_LOCAL_DICT_VERSION_COUNT * DICT_VERSION_INCREMENT
+
+static inline uint64_t
+dict_next_version(PyInterpreterState *interp)
+{
+ PyThreadState *tstate = PyThreadState_GET();
+ uint64_t cur_progress = (tstate->dict_global_version &
+ (THREAD_LOCAL_DICT_VERSION_BATCH - 1));
+ if (cur_progress == 0) {
+ uint64_t next = _Py_atomic_add_uint64(&interp->dict_state.global_version,
+ THREAD_LOCAL_DICT_VERSION_BATCH);
+ tstate->dict_global_version = next;
+ }
+ return tstate->dict_global_version += DICT_VERSION_INCREMENT;
+}
+
+#define DICT_NEXT_VERSION(INTERP) dict_next_version(INTERP)
#else
#define DICT_NEXT_VERSION(INTERP) \
diff --git a/Lib/test/test_free_threading/test_dict.py b/Lib/test/test_free_threading/test_dict.py
index 6a909dd..f877582 100644
--- a/Lib/test/test_free_threading/test_dict.py
+++ b/Lib/test/test_free_threading/test_dict.py
@@ -8,6 +8,8 @@ from functools import partial
from threading import Thread
from unittest import TestCase
+from _testcapi import dict_version
+
from test.support import threading_helper
@@ -137,5 +139,39 @@ class TestDict(TestCase):
for ref in thread_list:
self.assertIsNone(ref())
+ def test_dict_version(self):
+ THREAD_COUNT = 10
+ DICT_COUNT = 10000
+ lists = []
+ writers = []
+
+ def writer_func(thread_list):
+ for i in range(DICT_COUNT):
+ thread_list.append(dict_version({}))
+
+ for x in range(THREAD_COUNT):
+ thread_list = []
+ lists.append(thread_list)
+ writer = Thread(target=partial(writer_func, thread_list))
+ writers.append(writer)
+
+ for writer in writers:
+ writer.start()
+
+ for writer in writers:
+ writer.join()
+
+ total_len = 0
+ values = set()
+ for thread_list in lists:
+ for v in thread_list:
+ if v in values:
+ print('dup', v, (v/4096)%256)
+ values.add(v)
+ total_len += len(thread_list)
+ versions = set(dict_version for thread_list in lists for dict_version in thread_list)
+ self.assertEqual(len(versions), THREAD_COUNT*DICT_COUNT)
+
+
if __name__ == "__main__":
unittest.main()
diff --git a/Modules/_testcapi/dict.c b/Modules/_testcapi/dict.c
index 4319906..e80d898 100644
--- a/Modules/_testcapi/dict.c
+++ b/Modules/_testcapi/dict.c
@@ -1,7 +1,6 @@
#include "parts.h"
#include "util.h"
-
static PyObject *
dict_containsstring(PyObject *self, PyObject *args)
{
@@ -182,6 +181,18 @@ dict_popstring_null(PyObject *self, PyObject *args)
RETURN_INT(PyDict_PopString(dict, key, NULL));
}
+static PyObject *
+dict_version(PyObject *self, PyObject *dict)
+{
+ if (!PyDict_Check(dict)) {
+ PyErr_SetString(PyExc_TypeError, "expected dict");
+ return NULL;
+ }
+_Py_COMP_DIAG_PUSH
+_Py_COMP_DIAG_IGNORE_DEPR_DECLS
+ return PyLong_FromUnsignedLongLong(((PyDictObject *)dict)->ma_version_tag);
+_Py_COMP_DIAG_POP
+}
static PyMethodDef test_methods[] = {
{"dict_containsstring", dict_containsstring, METH_VARARGS},
@@ -193,6 +204,7 @@ static PyMethodDef test_methods[] = {
{"dict_pop_null", dict_pop_null, METH_VARARGS},
{"dict_popstring", dict_popstring, METH_VARARGS},
{"dict_popstring_null", dict_popstring_null, METH_VARARGS},
+ {"dict_version", dict_version, METH_O},
{NULL},
};
diff --git a/Python/pystate.c b/Python/pystate.c
index 7c75263..2f1521e 100644
--- a/Python/pystate.c
+++ b/Python/pystate.c
@@ -1488,6 +1488,7 @@ init_threadstate(_PyThreadStateImpl *_tstate,
tstate->datastack_limit = NULL;
tstate->what_event = -1;
tstate->previous_executor = NULL;
+ tstate->dict_global_version = 0;
tstate->delete_later = NULL;