Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork33.3k
gh-123271: Make builtin zip method safe under free-threading#123272
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Uh oh!
There was an error while loading.Please reload this page.
Conversation
Uh oh!
There was an error while loading.Please reload this page.
| @@ -0,0 +1 @@ | |||
| Make:meth:`zip` safe under free-threading. | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
| Make:meth:`zip` safeunder free-threading. | |
| Make:func:`zip`thread-safewithout the:term:`GIL`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Even with this PR thezip function is not thread-safe: when iterating overzip(range(100), range(100)) one can obtain a tuple(2,3). What does PR (hopefully) does is modifying the code so that the interpreter does not crash. For this reason I would like to be careful with the term "thread-safe". I choose not to makezip fully thread safe because of the performance impact, see the discussion in#120496
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Thanks for clarifying. The wordsafe is a bit confusing, I guess a lot of other people would read it asthread-safe. I think we need to clarify the reason whyzip isn't safe infree-threaded build.
Uh oh!
There was an error while loading.Please reload this page.
Co-authored-by: Kirill Podoprigora <kirill.bast9@mail.ru>
Python/bltinmodule.c Outdated
| #ifdefPy_GIL_DISABLED | ||
| intreuse_tuple=0; | ||
| #else | ||
| intreuse_tuple=Py_REFCNT(result)==1; | ||
| #endif | ||
| if (reuse_tuple) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
I think we want to enable re-use under certain conditions. We should probably put the logic in some internal function, possibly inpycore_object.h. I think the condition for the free-threaded build is:
- ob_tid matches
_Py_ThreadId() - ob_ref_local is 1
- ob_ref_shared is 0
The logic is that no other thread callingzip_next can re-uselz->result because of the thread id condition (condition 1). And no thread outside ofzip_next can incref that object because the combined conditions ensure thatlz->result is the only reference.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
@colesbury Thanks for the hint! I implementeded the suggestion
- I placed the logic in a single method
_Py_Reuse_Immutable_Objectso we can use the trick code for several other cases (e.g.itertools.pairwise,enumerate). The location and name of the new method can be changed though - I re-ordered the three conditions because I suspect that
ob_ref_localis most informative and fastest to check - I am now checking
ob_ref_sharedto be zero, but perhaps we should check onPy_ARITHMETIC_RIGHT_SHIFT(Py_ssize_t, ob_ref_shared , _Py_REF_SHARED_SHIFT)?
Misc/NEWS.d/next/Core_and_Builtins/2024-08-23-21-20-34.gh-issue-123271.xeVViR.rst OutdatedShow resolvedHide resolved
Uh oh!
There was an error while loading.Please reload this page.
…e-123271.xeVViR.rstCo-authored-by: Sam Gross <colesbury@gmail.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Thanks@eendebakpt. The check thatob_ref_shared is entirely zero is correct as you wrote it. We don't want non-zero flags in this case as that could allow another thread to concurrently incref the object in some circumstances.
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
| _= [t.start()fortinworker_threads] | ||
| _= [t.join()fortinworker_threads] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
The preferred style is to usefor loops instead of using list comprehensions for side effects.
| number_of_threads=8 | ||
| number_of_iterations=40 | ||
| n=40_000 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
We run lots of unit tests in CI, so they individually need to be very fast. For the free-threaded tests, we should aim for <0.1 seconds on two cores (i.e., when run withtaskset -c 0-1 on Linux)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
On my system (windows) the test is 0.07 seconds for the free-threading build. I will reduce the number of threads and iterations a bit so it is even faster.
Co-authored-by: Sam Gross <colesbury@gmail.com>
Thanks for the fix@eendebakpt! There's maybe a dozen or so similar patterns in CPython and I searched for the regex |
@colesbury Thanks for the guidance here! I was indeed planning on addressing some of the similar patterns. I'll open an new issue for this. |
Uh oh!
There was an error while loading.Please reload this page.
In this PR we make
zipsafe under free-threading.