FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

gh-112075: Use relaxed stores for places where we may race with when reading lock-free by DinoV · Pull Request #115786 · python/cpython · GitHub

/ cpython Public

gh-112075: Use relaxed stores for places where we may race with when reading lock-free - #115786

Merged
DinoV merged 2 commits into
python:mainfrom
DinoV:nogil_dict_atomicassign
Feb 28, 2024
Merged

gh-112075: Use relaxed stores for places where we may race with when reading lock-free#115786
DinoV merged 2 commits into
python:mainfrom
DinoV:nogil_dict_atomicassign

Conversation

DinoV commented Feb 21, 2024
edited by bedevere-app Bot
Loading

Copy link
Copy Markdown
Contributor

With lock-free reads and iterations we can read from some places w/o locks. This adds relaxed atomic stores to those places.

Comment thread Objects/dictobject.c Outdated
Comment on lines +238 to +240
#define STORE_KEY(ep, key) FT_ATOMIC_STORE_PTR_RELAXED(ep->me_key, key)
#define STORE_VALUE(ep, value) FT_ATOMIC_STORE_PTR_RELAXED(ep->me_value, value)
#define STORE_SPLIT_VALUE(mp, idx, value) FT_ATOMIC_STORE_PTR_RELAXED(mp->ma_values->values[idx], value)

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.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

I think we want "release", although I'm not 100% sure. It's possible that the synchronization from the incref is sufficient even with relaxes stores

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Maybe I've failed to capture exactly what you're concerned about, but I think the synchronization around keys and values provides what we need: https://gist.github.com/DinoV/fd83e80e5eb37ffa197dadd9791cca6e because reads to ma_keys and ma_values are fully sequential, and writes to them are done with release stores.

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.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

My concern is that the initialization of the fields within key or value may not be fully visible. I think it's probably okay, but only because our mutex implementation uses stronger orderings than conventional locks.

For example, let's say value is a PyLongObject:

In pseudo-code:

PyLongObject *value = ...;
value->ob_digit = 123456;  // (1) non-atomic store that I'm concerned about
...
Py_BEGIN_CRITICAL_SECTION(dict);  // (2) lock dict
PyDictKeyEntry *entries = DK_ENTRIES(dict->ma_keys);
entries[...]->me_value = value; // (3) relaxed store
Py_END_CRITICAL_SECTION();

My concern was that (1) would be reordered past (3). I think it's okay because the mutexes used by critical sections use sequential consistency for both locking and unlocking, so (1) can't be reordered past (2). The "conventional" ordering is "acquire" for lock and "release" for unlock, which would not be sufficient.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

And I think we'll be forced to lock on both threads because _Py_TryXGetRef is going to fail on the value that's not yet shared across threads and we'll be forced into the locking code path.

colesbury Feb 22, 2024
edited
Loading

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.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

After thinking about this a bit more, I think (3) really needs to be at least "release".

First, contrary to what I wrote above, the mutex locking isn't sufficient because a seq-cst operation on a variable is weaker than a seq-cst fence -- it doesn't ensure that the write in (1) is visible in this case.

I don't think we can rely on the _Py_TryXGetRef failing either. The object might have a non-zero shared refcount for a number of reasons.

Here's a simplified model. If you change the relevant "memory_order_release" to "memory_order_relaxed" it will fail:
https://github.com/colesbury/c11-model-checker/blob/cpython-models/test/dict_value.c

DinoV force-pushed the nogil_dict_atomicassign branch from 7b365a2 to 596ceb4 Compare February 22, 2024 17:45
DinoV force-pushed the nogil_dict_atomicassign branch from 8b4659f to 7c8cd5a Compare February 22, 2024 20:50
DinoV marked this pull request as ready for review February 23, 2024 17:21

colesbury 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.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

LGTM

DinoV merged commit 81c7996 into python:main Feb 28, 2024
woodruffw pushed a commit to woodruffw-forks/cpython that referenced this pull request Mar 4, 2024
adorilson pushed a commit to adorilson/cpython that referenced this pull request Mar 25, 2024
diegorusso pushed a commit to diegorusso/cpython that referenced this pull request Apr 17, 2024
DinoV deleted the nogil_dict_atomicassign branch May 31, 2024 18:23
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Sign up for free to 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.

2 participants


Back | FazBrowse Home | New Git URL