gh-112075: use per-thread dict version pool (#118676)
use thread state set of dict versions
This commit is contained in:
parent
723d4d2fe8
commit
ff6cbb2503
@ -188,6 +188,7 @@ struct _ts {
|
|||||||
|
|
||||||
PyObject *previous_executor;
|
PyObject *previous_executor;
|
||||||
|
|
||||||
|
uint64_t dict_global_version;
|
||||||
};
|
};
|
||||||
|
|
||||||
#ifdef Py_DEBUG
|
#ifdef Py_DEBUG
|
||||||
|
@ -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)
|
#define DICT_WATCHER_AND_MODIFICATION_MASK ((1 << (DICT_MAX_WATCHERS + DICT_WATCHED_MUTATION_BITS)) - 1)
|
||||||
|
|
||||||
#ifdef Py_GIL_DISABLED
|
#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
|
#else
|
||||||
#define DICT_NEXT_VERSION(INTERP) \
|
#define DICT_NEXT_VERSION(INTERP) \
|
||||||
|
@ -8,6 +8,8 @@ from functools import partial
|
|||||||
from threading import Thread
|
from threading import Thread
|
||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
|
|
||||||
|
from _testcapi import dict_version
|
||||||
|
|
||||||
from test.support import threading_helper
|
from test.support import threading_helper
|
||||||
|
|
||||||
|
|
||||||
@ -137,5 +139,39 @@ class TestDict(TestCase):
|
|||||||
for ref in thread_list:
|
for ref in thread_list:
|
||||||
self.assertIsNone(ref())
|
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__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
#include "parts.h"
|
#include "parts.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
dict_containsstring(PyObject *self, PyObject *args)
|
dict_containsstring(PyObject *self, PyObject *args)
|
||||||
{
|
{
|
||||||
@ -182,6 +181,18 @@ dict_popstring_null(PyObject *self, PyObject *args)
|
|||||||
RETURN_INT(PyDict_PopString(dict, key, NULL));
|
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[] = {
|
static PyMethodDef test_methods[] = {
|
||||||
{"dict_containsstring", dict_containsstring, METH_VARARGS},
|
{"dict_containsstring", dict_containsstring, METH_VARARGS},
|
||||||
@ -193,6 +204,7 @@ static PyMethodDef test_methods[] = {
|
|||||||
{"dict_pop_null", dict_pop_null, METH_VARARGS},
|
{"dict_pop_null", dict_pop_null, METH_VARARGS},
|
||||||
{"dict_popstring", dict_popstring, METH_VARARGS},
|
{"dict_popstring", dict_popstring, METH_VARARGS},
|
||||||
{"dict_popstring_null", dict_popstring_null, METH_VARARGS},
|
{"dict_popstring_null", dict_popstring_null, METH_VARARGS},
|
||||||
|
{"dict_version", dict_version, METH_O},
|
||||||
{NULL},
|
{NULL},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1488,6 +1488,7 @@ init_threadstate(_PyThreadStateImpl *_tstate,
|
|||||||
tstate->datastack_limit = NULL;
|
tstate->datastack_limit = NULL;
|
||||||
tstate->what_event = -1;
|
tstate->what_event = -1;
|
||||||
tstate->previous_executor = NULL;
|
tstate->previous_executor = NULL;
|
||||||
|
tstate->dict_global_version = 0;
|
||||||
|
|
||||||
tstate->delete_later = NULL;
|
tstate->delete_later = NULL;
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user