Skip to content

Commit 87c3446

Browse files
committed
Lock less and deal with atomic updates to shared size
1 parent 8c92d08 commit 87c3446

File tree

5 files changed

+65
-19
lines changed

5 files changed

+65
-19
lines changed

Include/cpython/pyatomic.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -469,6 +469,9 @@ _Py_atomic_store_int_release(int *obj, int value);
469469
static inline int
470470
_Py_atomic_load_int_acquire(const int *obj);
471471

472+
static inline Py_ssize_t
473+
_Py_atomic_load_ssize_acquire(const Py_ssize_t *obj);
474+
472475

473476
// --- _Py_atomic_fence ------------------------------------------------------
474477

Include/cpython/pyatomic_gcc.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,9 @@ static inline int
495495
_Py_atomic_load_int_acquire(const int *obj)
496496
{ return __atomic_load_n(obj, __ATOMIC_ACQUIRE); }
497497

498+
static inline Py_ssize_t
499+
_Py_atomic_load_ssize_acquire(const Py_ssize_t *obj)
500+
{ return __atomic_load_n(obj, __ATOMIC_ACQUIRE); }
498501

499502
// --- _Py_atomic_fence ------------------------------------------------------
500503

Include/cpython/pyatomic_msc.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -938,6 +938,17 @@ _Py_atomic_load_int_acquire(const int *obj)
938938
#endif
939939
}
940940

941+
static inline Py_ssize_t
942+
_Py_atomic_load_ssize_acquire(const Py_ssize_t *obj)
943+
{
944+
#if defined(_M_X64) || defined(_M_IX86)
945+
return *(Py_ssize_t volatile *)obj;
946+
#elif defined(_M_ARM64)
947+
return (Py_ssize_t)__ldar64((unsigned __int64 volatile *)obj);
948+
#else
949+
# error "no implementation of _Py_atomic_load_ssize_acquire"
950+
#endif
951+
}
941952

942953
// --- _Py_atomic_fence ------------------------------------------------------
943954

Include/cpython/pyatomic_std.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -870,6 +870,13 @@ _Py_atomic_load_int_acquire(const int *obj)
870870
memory_order_acquire);
871871
}
872872

873+
static inline Py_ssize_t
874+
_Py_atomic_load_ssize_acquire(const Py_ssize_t *obj)
875+
{
876+
_Py_USING_STD;
877+
return atomic_load_explicit((const _Atomic(Py_ssize_t)*)obj,
878+
memory_order_acquire);
879+
}
873880

874881

875882
// --- _Py_atomic_fence ------------------------------------------------------

Objects/dictobject.c

Lines changed: 41 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ ASSERT_DICT_LOCKED(PyObject *op)
151151
}
152152
#define ASSERT_DICT_LOCKED(op) ASSERT_DICT_LOCKED(_Py_CAST(PyObject*, op))
153153

154-
#define LOCK_KEYS(keys) PyMutex_Lock(&keys->dk_mutex)
154+
#define LOCK_KEYS(keys) PyMutex_LockFlags(&keys->dk_mutex, _Py_LOCK_DONT_DETACH)
155155
#define UNLOCK_KEYS(keys) PyMutex_Unlock(&keys->dk_mutex)
156156

157157
#define ASSERT_KEYS_LOCKED(keys) assert(PyMutex_IsLocked(&keys->dk_mutex))
@@ -161,6 +161,15 @@ ASSERT_DICT_LOCKED(PyObject *op)
161161
#define INCREF_KEYS(dk) _Py_atomic_add_ssize(&dk->dk_refcnt, 1)
162162
// Dec refs the keys object, giving the previous value
163163
#define DECREF_KEYS(dk) _Py_atomic_add_ssize(&dk->dk_refcnt, -1)
164+
static inline void split_keys_entry_added(PyDictKeysObject *keys)
165+
{
166+
ASSERT_KEYS_LOCKED(keys);
167+
168+
// We increase before we decrease so we never get too small of a value
169+
// when we're racing with reads
170+
_Py_atomic_store_ssize(&keys->dk_nentries, keys->dk_nentries + 1);
171+
_Py_atomic_store_ssize(&keys->dk_usable, keys->dk_usable - 1);
172+
}
164173

165174
#else /* Py_GIL_DISABLED */
166175

@@ -172,6 +181,11 @@ ASSERT_DICT_LOCKED(PyObject *op)
172181
#define STORE_SHARED_KEY(key, value) key = value
173182
#define INCREF_KEYS(dk) dk->dk_refcnt++
174183
#define DECREF_KEYS(dk) dk->dk_refcnt--
184+
static inline void split_keys_entry_added(PyDictKeysObject *keys)
185+
{
186+
keys->dk_usable--;
187+
keys->dk_nentries++;
188+
}
175189

176190
#endif
177191

@@ -809,15 +823,25 @@ new_dict(PyInterpreterState *interp,
809823
static inline size_t
810824
shared_keys_usable_size(PyDictKeysObject *keys)
811825
{
812-
ASSERT_KEYS_LOCKED(keys);
826+
#ifdef Py_GIL_DISABLED
827+
// dk_usable will decrease for each instance that is created and each
828+
// value that is added. dk_entries will increase for each value that
829+
// is added. We want to always return the right value or larger.
830+
// We therefore increase dk_entries first and we decrease dk_usable
831+
// second, and conversely here we read dk_usable first and dk_entries
832+
// second (to avoid the case where we read entries before the increment
833+
// and read usable after the decrement)
834+
return (size_t)(_Py_atomic_load_ssize_acquire(&keys->dk_usable) +
835+
_Py_atomic_load_ssize_acquire(&keys->dk_nentries));
836+
#else
813837
return (size_t)keys->dk_nentries + (size_t)keys->dk_usable;
838+
#endif
814839
}
815840

816841
/* Consumes a reference to the keys object */
817842
static PyObject *
818843
new_dict_with_shared_keys(PyInterpreterState *interp, PyDictKeysObject *keys)
819844
{
820-
LOCK_KEYS(keys);
821845
size_t size = shared_keys_usable_size(keys);
822846
PyDictValues *values = new_values(size);
823847
if (values == NULL) {
@@ -830,7 +854,6 @@ new_dict_with_shared_keys(PyInterpreterState *interp, PyDictKeysObject *keys)
830854
values->values[i] = NULL;
831855
}
832856
PyObject *res = new_dict(interp, keys, values, 0, 1);
833-
UNLOCK_KEYS(keys);
834857
return res;
835858
}
836859

@@ -1269,8 +1292,7 @@ insert_into_dictkeys(PyDictKeysObject *keys, PyObject *name)
12691292
dictkeys_set_index(keys, hashpos, ix);
12701293
assert(ep->me_key == NULL);
12711294
ep->me_key = Py_NewRef(name);
1272-
keys->dk_usable--;
1273-
keys->dk_nentries++;
1295+
split_keys_entry_added(keys);
12741296
}
12751297
assert (ix < SHARED_KEYS_MAX_SIZE);
12761298
return ix;
@@ -1279,7 +1301,7 @@ insert_into_dictkeys(PyDictKeysObject *keys, PyObject *name)
12791301

12801302
static inline int
12811303
insert_combined_dict(PyInterpreterState *interp, PyDictObject *mp,
1282-
Py_hash_t hash, PyObject *key, PyObject *value, int unicode)
1304+
Py_hash_t hash, PyObject *key, PyObject *value, int unicode)
12831305
{
12841306
if (mp->ma_keys->dk_usable <= 0) {
12851307
/* Need to resize. */
@@ -1340,8 +1362,7 @@ insert_split_dict(PyInterpreterState *interp, PyDictObject *mp,
13401362
assert (mp->ma_values->values[index] == NULL);
13411363
mp->ma_values->values[index] = value;
13421364

1343-
keys->dk_usable--;
1344-
keys->dk_nentries++;
1365+
split_keys_entry_added(keys);
13451366
assert(keys->dk_usable >= 0);
13461367
UNLOCK_KEYS(keys);
13471368
return 0;
@@ -3471,9 +3492,7 @@ copy_lock_held(PyObject *o)
34713492

34723493
if (_PyDict_HasSplitTable(mp)) {
34733494
PyDictObject *split_copy;
3474-
LOCK_KEYS(mp->ma_keys);
34753495
size_t size = shared_keys_usable_size(mp->ma_keys);
3476-
UNLOCK_KEYS(mp->ma_keys);
34773496
PyDictValues *newvalues = new_values(size);
34783497
if (newvalues == NULL)
34793498
return PyErr_NoMemory();
@@ -3608,7 +3627,7 @@ dict_equal_lock_held(PyDictObject *a, PyDictObject *b)
36083627
Py_hash_t hash;
36093628
if (DK_IS_UNICODE(a->ma_keys)) {
36103629
PyDictUnicodeEntry *ep = &DK_UNICODE_ENTRIES(a->ma_keys)[i];
3611-
key = LOAD_SHARED_KEY(ep->me_key);
3630+
key = ep->me_key;
36123631
if (key == NULL) {
36133632
continue;
36143633
}
@@ -3995,8 +4014,8 @@ dict_popitem_impl(PyDictObject *self)
39954014
LOCK_KEYS(keys);
39964015

39974016
int status = dictresize(interp, self, DK_LOG_SIZE(self->ma_keys), 1);
3998-
dictkeys_decref(interp, keys);
39994017
UNLOCK_KEYS(keys);
4018+
dictkeys_decref(interp, keys);
40004019

40014020
if (status < 0) {
40024021
Py_DECREF(res);
@@ -4103,9 +4122,7 @@ sizeof_lock_held(PyDictObject *mp)
41034122
{
41044123
size_t res = _PyObject_SIZE(Py_TYPE(mp));
41054124
if (_PyDict_HasSplitTable(mp)) {
4106-
LOCK_KEYS(mp->ma_keys);
41074125
res += shared_keys_usable_size(mp->ma_keys) * sizeof(PyObject*);
4108-
UNLOCK_KEYS(mp->ma_keys);
41094126
}
41104127
/* If the dictionary is split, the keys portion is accounted-for
41114128
in the type object. */
@@ -6030,12 +6047,19 @@ _PyObject_InitInlineValues(PyObject *obj, PyTypeObject *tp)
60306047
assert(tp->tp_flags & Py_TPFLAGS_MANAGED_DICT);
60316048
PyDictKeysObject *keys = CACHED_KEYS(tp);
60326049
assert(keys != NULL);
6050+
#ifdef Py_GIL_DISABLED
6051+
Py_ssize_t usable = keys->dk_usable;
6052+
while (usable > 1) {
6053+
if (_Py_atomic_compare_exchange_ssize(&keys->dk_usable, &usable, usable - 1)) {
6054+
break;
6055+
}
6056+
}
6057+
#else
60336058
if (keys->dk_usable > 1) {
60346059
keys->dk_usable--;
60356060
}
6036-
LOCK_KEYS(keys);
6061+
#endif
60376062
size_t size = shared_keys_usable_size(keys);
6038-
UNLOCK_KEYS(keys);
60396063
PyDictValues *values = new_values(size);
60406064
if (values == NULL) {
60416065
PyErr_NoMemory();
@@ -6085,9 +6109,7 @@ make_dict_from_instance_attributes(PyInterpreterState *interp,
60856109
dictkeys_incref(keys);
60866110
Py_ssize_t used = 0;
60876111
Py_ssize_t track = 0;
6088-
LOCK_KEYS(keys);
60896112
size_t size = shared_keys_usable_size(keys);
6090-
UNLOCK_KEYS(keys);
60916113
for (size_t i = 0; i < size; i++) {
60926114
PyObject *val = values->values[i];
60936115
if (val != NULL) {

0 commit comments

Comments
 (0)