Skip to content

gh-112075: Make PyDictKeysObject thread-safe - #114741

Merged
DinoV merged 6 commits into
python:mainfrom
DinoV:nogil_dict_pydictkeys
Feb 21, 2024
Merged

gh-112075: Make PyDictKeysObject thread-safe#114741
DinoV merged 6 commits into
python:mainfrom
DinoV:nogil_dict_pydictkeys

Conversation

@DinoV

@DinoVDinoV commented Jan 30, 2024

Copy link
Copy Markdown
Contributor

Comment threadObjects/dictobject.c Outdated
_Py_DecRefTotal(_PyInterpreterState_GET());
#endif
if (--dk->dk_refcnt== 0) {
if (_Py_atomic_add_ssize(&dk->dk_refcnt, -1) == 1) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about adding a macro for the free-threading version and the default version?

@corona10corona10Jan 30, 2024

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did similar approach at listobject: 393cbef

See: _Py_SET_ITEMREF

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@corona10 is there a significant difference in performance between the two builds? I'm not sure if it is worth it to add these preprocessor guards everywhere if there is no measurable effect :) Is there a different motivation than performance?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regardless of whether we do the macro approach everywhere, I think it's a good idea in this specific case (specifically for performance).

@DinoV
DinoVforce-pushed the nogil_dict_pydictkeys branch from 137eebe to 0891529CompareJanuary 30, 2024 17:31
@DinoV
DinoV marked this pull request as ready for review January 30, 2024 18:05
Comment threadObjects/dictobject.c Outdated
@DinoV
DinoVforce-pushed the nogil_dict_pydictkeys branch from 0891529 to 398cb23CompareJanuary 31, 2024 17:04
@colesbury
colesbury self-requested a review January 31, 2024 20:15

@colesburycolesbury left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This mostly looks good to me.

I don't think we want to be locking around shared_keys_usable_size(). It's both not sufficient for thread-safety at most of the call sites and not what we want to be doing for performance reasons.

Comment threadObjects/dictobject.c Outdated
Comment threadObjects/dictobject.c
Comment threadObjects/dictobject.c Outdated
Comment threadObjects/dictobject.c Outdated
Comment threadObjects/dictobject.c Outdated
Comment threadObjects/dictobject.c Outdated
Comment threadObjects/dictobject.c Outdated
Comment threadObjects/dictobject.c Outdated
Comment threadObjects/dictobject.c Outdated
Comment on lines +170 to +171
_Py_atomic_store_ssize(&keys->dk_nentries, keys->dk_nentries + 1);
_Py_atomic_store_ssize(&keys->dk_usable, keys->dk_usable - 1);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can use a weaker ordering here that will be faster, especially on x86 where "release" doesn't require any memory barrier:

 _Py_atomic_store_ssize_relaxed(&keys->dk_nentries, keys->dk_nentries + 1);
_Py_atomic_store_ssize_release(&keys->dk_usable, keys->dk_usable - 1);

(I don't think we have _Py_atomic_store_ssize_release yet, though)

I find the memory orderings hard to reason correctly about, so I like to model them with CDSChecker. Here's the model I used for this:
https://github.com/colesbury/c11-model-checker/blob/cpython-models/test/gh-112075.c

Comment threadObjects/dictobject.c Outdated
Comment threadObjects/dictobject.c Outdated
Comment threadObjects/dictobject.c
Comment threadObjects/dictobject.c Outdated
Comment threadObjects/dictobject.c Outdated
Comment threadObjects/dictobject.c Outdated
Comment threadObjects/dictobject.c
Comment threadInclude/cpython/pyatomic_msc.h Outdated
Comment threadInclude/cpython/pyatomic_msc.h Outdated
Comment threadObjects/dictobject.c Outdated
Comment threadObjects/dictobject.c Outdated

@colesburycolesbury left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One minor formatting comment, but otherwise LGTM

Comment threadObjects/dictobject.c Outdated
if (_PyDict_HasSplitTable(mp)) {
LOCK_KEYS(keys);
dictkeys_incref(keys);
} else {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
}else {
}
else {

@DinoV
DinoVforce-pushed the nogil_dict_pydictkeys branch from 808dd89 to 4914ba8CompareFebruary 20, 2024 20:01
@DinoV
DinoVforce-pushed the nogil_dict_pydictkeys branch 2 times, most recently from 90bea6a to 88ab576CompareFebruary 20, 2024 23:25
@DinoV
DinoVforce-pushed the nogil_dict_pydictkeys branch from 88ab576 to a9d3666CompareFebruary 21, 2024 00:12
@DinoV
DinoV merged commit 176df09 into python:mainFeb 21, 2024
woodruffw pushed a commit to woodruffw-forks/cpython that referenced this pull request Mar 4, 2024
Adds locking for shared PyDictKeysObject's for dictionaries
diegorusso pushed a commit to diegorusso/cpython that referenced this pull request Apr 17, 2024
Adds locking for shared PyDictKeysObject's for dictionaries
@DinoV
DinoV deleted the nogil_dict_pydictkeys branch May 31, 2024 18:23
LukasWoodtli pushed a commit to LukasWoodtli/cpython that referenced this pull request Jan 22, 2025
Adds locking for shared PyDictKeysObject's for dictionaries
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@DinoV@methane@colesbury@corona10@erlend-aasland