diff options
author | Dino Viehland <dinoviehland@meta.com> | 2024-05-07 00:22:26 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-05-07 00:22:26 (GMT) |
commit | ff6cbb2503a8fe3fceeadd889e34fc9a8f308ecd (patch) | |
tree | ed2ab05ad2ca3f3819babe399933ffcb85c93ad4 | |
parent | 723d4d2fe8e77b398f0ccffcfa541149caaac6a1 (diff) | |
download | cpython-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.h | 1 | ||||
-rw-r--r-- | Include/internal/pycore_dict.h | 21 | ||||
-rw-r--r-- | Lib/test/test_free_threading/test_dict.py | 36 | ||||
-rw-r--r-- | Modules/_testcapi/dict.c | 14 | ||||
-rw-r--r-- | Python/pystate.c | 1 |
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; |