Skip to content

Commit

Permalink
gh-112075: use per-thread dict version pool (#118676)
Browse files Browse the repository at this point in the history
use thread state set of dict versions
  • Loading branch information
DinoV authored May 7, 2024
1 parent 723d4d2 commit ff6cbb2
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 3 deletions.
1 change: 1 addition & 0 deletions Include/cpython/pystate.h
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ struct _ts {

PyObject *previous_executor;

uint64_t dict_global_version;
};

#ifdef Py_DEBUG
Expand Down
21 changes: 19 additions & 2 deletions Include/internal/pycore_dict.h
Original file line number Diff line number Diff line change
Expand Up @@ -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) \
Expand Down
36 changes: 36 additions & 0 deletions Lib/test/test_free_threading/test_dict.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
from threading import Thread
from unittest import TestCase

from _testcapi import dict_version

from test.support import threading_helper


Expand Down Expand Up @@ -137,5 +139,39 @@ def writer_func(l):
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()
14 changes: 13 additions & 1 deletion Modules/_testcapi/dict.c
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
#include "parts.h"
#include "util.h"


static PyObject *
dict_containsstring(PyObject *self, PyObject *args)
{
Expand Down Expand Up @@ -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},
Expand All @@ -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},
};

Expand Down
1 change: 1 addition & 0 deletions Python/pystate.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down

0 comments on commit ff6cbb2

Please sign in to comment.