Skip to content

Commit f405821

Browse files
committed
use thread state set of dict versions
1 parent e5353d4 commit f405821

File tree

5 files changed

+85
-3
lines changed

5 files changed

+85
-3
lines changed

Include/cpython/pystate.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ struct _ts {
188188

189189
PyObject *previous_executor;
190190

191+
uint64_t dict_global_version;
191192
};
192193

193194
#ifdef Py_DEBUG

Include/internal/pycore_dict.h

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,8 +221,27 @@ static inline PyDictUnicodeEntry* DK_UNICODE_ENTRIES(PyDictKeysObject *dk) {
221221
#define DICT_WATCHER_AND_MODIFICATION_MASK ((1 << (DICT_MAX_WATCHERS + DICT_WATCHED_MUTATION_BITS)) - 1)
222222

223223
#ifdef Py_GIL_DISABLED
224-
#define DICT_NEXT_VERSION(INTERP) \
225-
(_Py_atomic_add_uint64(&(INTERP)->dict_state.global_version, DICT_VERSION_INCREMENT) + DICT_VERSION_INCREMENT)
224+
225+
#define THREAD_LOCAL_DICT_VERSION_COUNT 256
226+
#define THREAD_LOCAL_DICT_VERSION_BATCH THREAD_LOCAL_DICT_VERSION_COUNT * DICT_VERSION_INCREMENT
227+
228+
static inline uint64_t
229+
dict_next_version(PyInterpreterState *interp)
230+
{
231+
PyThreadState *tstate = PyThreadState_GET();
232+
uint64_t cur_progress = (tstate->dict_global_version &
233+
(THREAD_LOCAL_DICT_VERSION_BATCH - 1));
234+
235+
if (cur_progress == 0) {
236+
uint64_t next = _Py_atomic_add_uint64(&interp->dict_state.global_version,
237+
THREAD_LOCAL_DICT_VERSION_BATCH);
238+
tstate->dict_global_version = next + THREAD_LOCAL_DICT_VERSION_BATCH;
239+
return next;
240+
}
241+
return tstate->dict_global_version += DICT_VERSION_INCREMENT;
242+
}
243+
244+
#define DICT_NEXT_VERSION(INTERP) dict_next_version(INTERP)
226245

227246
#else
228247
#define DICT_NEXT_VERSION(INTERP) \
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import unittest
2+
3+
from functools import partial
4+
from threading import Thread
5+
from unittest import TestCase
6+
7+
from _testcapi import dict_version
8+
9+
from test.support import threading_helper
10+
11+
12+
@threading_helper.requires_working_threading()
13+
class TestDict(TestCase):
14+
def test_dict_version(self):
15+
THREAD_COUNT = 10
16+
DICT_COUNT = 10000
17+
lists = []
18+
writers = []
19+
20+
def writer_func(thread_list):
21+
for i in range(DICT_COUNT):
22+
thread_list.append(dict_version({}))
23+
24+
for x in range(THREAD_COUNT):
25+
thread_list = []
26+
lists.append(thread_list)
27+
writer = Thread(target=partial(writer_func, thread_list))
28+
writers.append(writer)
29+
30+
for writer in writers:
31+
writer.start()
32+
33+
for writer in writers:
34+
writer.join()
35+
36+
total_len = 0
37+
values = set()
38+
for thread_list in lists:
39+
for v in thread_list:
40+
if v in values:
41+
print('dup', v, (v/4096)%256)
42+
values.add(v)
43+
total_len += len(thread_list)
44+
versions = set(dict_version for thread_list in lists for dict_version in thread_list)
45+
self.assertEqual(len(versions), THREAD_COUNT*DICT_COUNT)
46+
47+
48+
if __name__ == "__main__":
49+
unittest.main()

Modules/_testcapi/dict.c

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
#include "parts.h"
22
#include "util.h"
33

4-
54
static PyObject *
65
dict_containsstring(PyObject *self, PyObject *args)
76
{
@@ -182,6 +181,18 @@ dict_popstring_null(PyObject *self, PyObject *args)
182181
RETURN_INT(PyDict_PopString(dict, key, NULL));
183182
}
184183

184+
static PyObject *
185+
dict_version(PyObject *self, PyObject *dict)
186+
{
187+
if (!PyDict_Check(dict)) {
188+
PyErr_SetString(PyExc_TypeError, "expected dict");
189+
return NULL;
190+
}
191+
_Py_COMP_DIAG_PUSH
192+
_Py_COMP_DIAG_IGNORE_DEPR_DECLS
193+
return PyLong_FromUnsignedLongLong(((PyDictObject *)dict)->ma_version_tag);
194+
_Py_COMP_DIAG_POP
195+
}
185196

186197
static PyMethodDef test_methods[] = {
187198
{"dict_containsstring", dict_containsstring, METH_VARARGS},
@@ -193,6 +204,7 @@ static PyMethodDef test_methods[] = {
193204
{"dict_pop_null", dict_pop_null, METH_VARARGS},
194205
{"dict_popstring", dict_popstring, METH_VARARGS},
195206
{"dict_popstring_null", dict_popstring_null, METH_VARARGS},
207+
{"dict_version", dict_version, METH_O},
196208
{NULL},
197209
};
198210

Python/pystate.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1487,6 +1487,7 @@ init_threadstate(_PyThreadStateImpl *_tstate,
14871487
tstate->datastack_limit = NULL;
14881488
tstate->what_event = -1;
14891489
tstate->previous_executor = NULL;
1490+
tstate->dict_global_version = 0;
14901491

14911492
tstate->delete_later = NULL;
14921493

0 commit comments

Comments
 (0)