GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — gh-103323: Get the "Current" Thread State from a Thread-Local Variable by ericsnowcurrently · Pull Request #103324 · python/cpython

Conversation

@ericsnowcurrently

@ericsnowcurrently ericsnowcurrently commented Apr 6, 2023 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member

We replace _PyRuntime.tstate_current with a thread-local variable. As part of this change, we add a _Py_thread_local macro in pyport.h (only for the core runtime) to smooth out the compiler differences. The main motivation here is in support of a per-interpreter GIL, but this change also provides some performance improvement opportunities.

Note that we do not provide a fallback to the thread-local, either falling back to the old tstate_current or to thread-specific storage (PyThread_tss_*()). If that proves problematic then we can circle back. I consider it unlikely, but will run the buildbots to double-check.

Also note that this does not change any of the code related to the GILState API, where it uses a thread state stored in thread-specific storage. I suspect we can combine that with _Py_tss_tstate (from here). However, that can be addressed separately and is not urgent (nor critical).

My only remaining uncertainty is with the existing "GIL is held" constraint. With _PyRuntime.tstate_current, it was only guaranteed valid in the thread currently holding the GIL, if any. With this change, it is valid even when the GIL isn't held. I don't see how that would be a problem, but I'm going to double-check anyway.

(While this change was mostly done independently, I did take some inspiration from earlier (~2020) work by @markshannon (main...markshannon:threadstate_in_tls) and @vstinner (#23976).)

  • Issue: Get the "Current" Thread State from a Thread-local #103323

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
ericsnowcurrently added 2 commits April 6, 2023 16:03
@ericsnowcurrently
Add a thread_local macro.
c4db85a
@ericsnowcurrently
tstate_current -> thread_local.
47a7094
@ericsnowcurrently ericsnowcurrently added the skip news label Apr 6, 2023
@bedevere-bot bedevere-bot mentioned this pull request Apr 6, 2023
Get the "Current" Thread State from a Thread-local #103323
Closed
@bedevere-bot bedevere-bot added the awaiting core review label Apr 6, 2023
@ericsnowcurrently

ericsnowcurrently commented Apr 7, 2023 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member Author

Per the benchmarks, this change is a little faster (less than 1%) on Linux/GCC.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

ericsnowcurrently added 9 commits April 7, 2023 07:58
@ericsnowcurrently
Add _PyThraedState_GetCurrent().
cf22de1
@ericsnowcurrently
Add HAVE_THREAD_LOCAL.
d4136d2
@ericsnowcurrently
Support the faster approach, if available.
f8c6598
@ericsnowcurrently
Do not fail if thread_local not supported.
9496df0
@ericsnowcurrently
thread_local -> _Py_thread_local
2c335a3
@ericsnowcurrently
Only define _Py_thread_local for the core runtime.
4af0ce7
@ericsnowcurrently
Fix pystate.c.
3db4007
@ericsnowcurrently
Call _PyThreadState_GET() from _PyRuntimeState_GetThreadState().
d573053
@ericsnowcurrently
Fix the error message.
feb8ef5
@ericsnowcurrently
ericsnowcurrently marked this pull request as ready for review April 7, 2023 18:14
@ericsnowcurrently ericsnowcurrently added the 🔨 test-with-buildbots Test PR w/ buildbots; report in status section label Apr 7, 2023
@bedevere-bot

bedevere-bot commented Apr 7, 2023

Copy link
Copy Markdown

🤖 New build scheduled with the buildbot fleet by @ericsnowcurrently for commit feb8ef5 🤖

If you want to schedule another build, you need to add the 🔨 test-with-buildbots label again.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@bedevere-bot bedevere-bot removed the 🔨 test-with-buildbots Test PR w/ buildbots; report in status section label Apr 7, 2023
@ericsnowcurrently
Add a NEWS entry.
2332a2e
@ericsnowcurrently ericsnowcurrently removed the skip news label Apr 7, 2023
@ericsnowcurrently
ericsnowcurrently requested a review from markshannon April 7, 2023 18:20
@markshannon

markshannon commented Apr 11, 2023

Copy link
Copy Markdown
Member

It might be worth trying to see what is the performance impact of storing the interpreter state in TLS as well.
No need to do that in this PR, though.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@ericsnowcurrently
ericsnowcurrently merged commit f8abfa3 into python:main Apr 24, 2023
@bedevere-bot bedevere-bot removed the awaiting core review label Apr 24, 2023
@ericsnowcurrently
ericsnowcurrently deleted the tstate_current-as-thread_local branch April 24, 2023 17:17
carljm added a commit to carljm/cpython that referenced this pull request Apr 24, 2023
@carljm
Merge branch 'main' into superopt
19b8025
* main: (53 commits)
  pythongh-102498 Clean up unused variables and imports in the email module  (python#102482)
  pythongh-99184: Bypass instance attribute access in `repr` of `weakref.ref` (python#99244)
  pythongh-99032: datetime docs: Encoding is no longer relevant (python#93365)
  pythongh-94300: Update datetime.strptime documentation (python#95318)
  pythongh-103776: Remove explicit uses of $(SHELL) from Makefile (pythonGH-103778)
  pythongh-87092: fix a few cases of incorrect error handling in compiler (python#103456)
  pythonGH-103727: Avoid advancing tokenizer too far in f-string mode (pythonGH-103775)
  Revert "Add tests for empty range equality (python#103751)" (python#103770)
  pythongh-94518: Port 23-argument `_posixsubprocess.fork_exec` to Argument Clinic (python#94519)
  pythonGH-65022: Fix description of copyreg.pickle function (python#102656)
  pythongh-103323: Get the "Current" Thread State from a Thread-Local Variable (pythongh-103324)
  pythongh-91687: modernize dataclass example typing (python#103773)
  pythongh-103746: Test `types.UnionType` and `Literal` types together (python#103747)
  pythongh-103765: Fix 'Warning: py:class reference target not found: ModuleSpec' (pythonGH-103769)
  pythongh-87452: Improve the Popen.returncode docs
  Removed unnecessary escaping of asterisks (python#103714)
  pythonGH-102973: Slim down Fedora packages in the dev container (python#103283)
  pythongh-103091: Add PyUnstable_Type_AssignVersionTag (python#103095)
  Add tests for empty range equality (python#103751)
  pythongh-103712: Increase the length of the type name in AttributeError messages (python#103713)
  ...
carljm added a commit to carljm/cpython that referenced this pull request Apr 24, 2023
@carljm
Merge branch 'superopt' into superopt_spec
1972ca4
* superopt: (82 commits)
  pythongh-101517: fix line number propagation in code generated for except* (python#103550)
  pythongh-103780: Use patch instead of mock in asyncio unix events test (python#103782)
  pythongh-102498 Clean up unused variables and imports in the email module  (python#102482)
  pythongh-99184: Bypass instance attribute access in `repr` of `weakref.ref` (python#99244)
  pythongh-99032: datetime docs: Encoding is no longer relevant (python#93365)
  pythongh-94300: Update datetime.strptime documentation (python#95318)
  pythongh-103776: Remove explicit uses of $(SHELL) from Makefile (pythonGH-103778)
  pythongh-87092: fix a few cases of incorrect error handling in compiler (python#103456)
  pythonGH-103727: Avoid advancing tokenizer too far in f-string mode (pythonGH-103775)
  Revert "Add tests for empty range equality (python#103751)" (python#103770)
  pythongh-94518: Port 23-argument `_posixsubprocess.fork_exec` to Argument Clinic (python#94519)
  pythonGH-65022: Fix description of copyreg.pickle function (python#102656)
  pythongh-103323: Get the "Current" Thread State from a Thread-Local Variable (pythongh-103324)
  pythongh-91687: modernize dataclass example typing (python#103773)
  pythongh-103746: Test `types.UnionType` and `Literal` types together (python#103747)
  pythongh-103765: Fix 'Warning: py:class reference target not found: ModuleSpec' (pythonGH-103769)
  pythongh-87452: Improve the Popen.returncode docs
  Removed unnecessary escaping of asterisks (python#103714)
  pythonGH-102973: Slim down Fedora packages in the dev container (python#103283)
  pythongh-103091: Add PyUnstable_Type_AssignVersionTag (python#103095)
  ...
@ericsnowcurrently
ericsnowcurrently restored the tstate_current-as-thread_local branch April 25, 2023 15:59
@ericsnowcurrently

ericsnowcurrently commented Apr 25, 2023

Copy link
Copy Markdown
Member Author

FTR, on Windows this introduced a ~2% performance regression, and on MacOS there's ~3% regression.

Note that these penalties may be partially mitigated by passing the current thread state as an argument throughout the internal C-API (where currently we only do so in some places). The implementation here is also relatively naïve. There are likely opportunities to improve performance via compiler-specific directives.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@ericsnowcurrently
ericsnowcurrently deleted the tstate_current-as-thread_local branch April 25, 2023 20:52
vstinner
vstinner reviewed May 4, 2023
View reviewed changes
Comment thread Include/internal/pycore_pystate.h
static inline PyThreadState*
_PyRuntimeState_GetThreadState(_PyRuntimeState *Py_UNUSED(runtime))
{
return _PyThreadState_GET();

@vstinner vstinner May 4, 2023

Copy link
Copy Markdown
Member

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.

This function no longer makes sense: I wrote PR #104171 to remove it.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

ericsnowcurrently reacted with thumbs up emoji
All reactions
  • 👍 1 reaction
@vstinner

vstinner commented May 4, 2023

Copy link
Copy Markdown
Member

(While this change was mostly done independently, I did take some inspiration from earlier (~2020) work by @markshannon (main...markshannon:threadstate_in_tls) and @vstinner (#23976).)

I prepared this change for years in advance:

  • Python 3.8:

    • I modified the PyThreadState_GET() macro to make it an alias to PyThreadState_Get() function: so it's always a function call, and no longer a macro to hide implementation details.
    • I added _PyThreadState_GET() static inline function to the internal C API to get more freedom on its implementation.
    • I modified many internal C functions to pass explicitly the tstate variable: Pass the Python thread state explicitly. For example, I added _PyErr_Occurred(tstate) function which replaces PyErr_Occurred() (which has no argument). The idea is that in the future, calling _PyThreadState_GET() may before slower if the value is retreived from a thread local storage (TLS) variable: which is done in this PR.
    • I modified many functions to pass the "state" more explicitly: runtime, tstate and/or interp. Example: _PySys_Create(runtime, interp, &sysmod) call in Python/pylifecycle.c.
  • Python 3.9:

    • I converted _PyThreadState_GET() macro to a static inline function.
    • I added _PyInterpreterState_GET() static inline function. Maybe the implementation will change in the future to be more efficient (ex: thread local storage?).
ericsnowcurrently reacted with heart emoji
All reactions
  • ❤️ 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@vstinner

vstinner commented May 4, 2023

Copy link
Copy Markdown
Member

My only remaining uncertainty is with the existing "GIL is held" constraint. With _PyRuntime.tstate_current, it was only guaranteed valid in the thread currently holding the GIL, if any. With this change, it is valid even when the GIL isn't held. I don't see how that would be a problem, but I'm going to double-check anyway.

In the Python 3.9 and 3.10 era, I moved multiple global states to the "interpreter state" (interp): https://pythondev.readthedocs.io/subinterpreters.html#done These changes caused various crashes in third party C extensions which use the C API with the GIL released (!). For example, calling PyLong_FromLong(1) with the GIL released. This was always illegal and invalid according to the C API documentation. But you know, there are always bugs in the wild. All affected C extensions have been fixed in the meanwhile. Also, some states were made global again (small integer singletons), and immortal objects also made the situation differnet.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@vstinner

vstinner commented May 4, 2023

Copy link
Copy Markdown
Member

FTR, on Windows this introduced a ~2% performance regression, and on MacOS there's ~3% regression.

It might be interesting to check the hot code calling _PyThreadState_GET() and see if tstate could be passed to only call _PyThreadState_GET() once. I'm not sure if it's worth it. Also, in stdlib C extensions, I would prefer to use the internal C API less rather than more :-)

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

vstinner
vstinner reviewed May 4, 2023
View reviewed changes
Comment thread Include/pyport.h
# define WITH_THREAD
#endif

#ifdef WITH_THREAD

@vstinner vstinner May 4, 2023

Copy link
Copy Markdown
Member

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.

This test is useless. This macro is now always defined. It's only kept for backward compatibility.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@ericsnowcurrently ericsnowcurrently May 4, 2023

Copy link
Copy Markdown
Member Author

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.

Doesn't it affect WASM builds?

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@vstinner vstinner May 4, 2023

Copy link
Copy Markdown
Member

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.

See the code 3 lines above:

#ifndef WITH_THREAD
#  define WITH_THREAD
#endif

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

ericsnowcurrently reacted with thumbs up emoji
All reactions
  • 👍 1 reaction
vstinner
vstinner reviewed May 4, 2023
View reviewed changes
Comment thread Include/internal/pycore_pystate.h
#if defined(HAVE_THREAD_LOCAL) && !defined(Py_BUILD_CORE_MODULE)
extern _Py_thread_local PyThreadState *_Py_tss_tstate;
#endif
PyAPI_DATA(PyThreadState *) _PyThreadState_GetCurrent(void);

@vstinner vstinner May 4, 2023

Copy link
Copy Markdown
Member

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.

What is the use case for this new _PyThreadState_GetCurrent() function? There is already PyThreadState_Get(). How is it different?

The API to get the current thread state is already complicated and has a complicated history: https://pythondev.readthedocs.io/pystate.html

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@ericsnowcurrently ericsnowcurrently May 4, 2023

Copy link
Copy Markdown
Member Author

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.

It's there because I couldn't find a way to mix PyAPI_DATA with _Py_thread_local. Looks like that's the same issue you ran into in 2020.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@vstinner

vstinner commented May 4, 2023

Copy link
Copy Markdown
Member

Does this change fix indirectly the PyGILState API for subinterpreters? See: #59956

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@vstinner

vstinner commented May 4, 2023

Copy link
Copy Markdown
Member

Thanks @ericsnowcurrently for taking care of this very old project!

It seems like the !defined(Py_BUILD_CORE_MODULE) test in pycore_pystate.h avoids the complicated linker issuses that I had on Windows and macOS when I tried a similar change in 2020 (PR #23976).

ericsnowcurrently reacted with thumbs up emoji
All reactions
  • 👍 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@neonene neonene mentioned this pull request Aug 9, 2024
Faster access to per-interpreter globals. faster-cpython/ideas#692
Open
@neonene neonene mentioned this pull request Aug 20, 2024
gh-117578: Fix inlining regression in PyType_GetModuleByDef() #123100
Closed
@ZeroIntensity ZeroIntensity mentioned this pull request Oct 27, 2025
gh-140544: Always assume that thread locals are available #140690
Merged
ZeroIntensity added a commit that referenced this pull request Oct 28, 2025
@ZeroIntensity
gh-140544: Always assume that thread locals are available (GH-140690)
2cefa70
Python has required thread local support since 3.12 (see GH-103324). By assuming that thread locals are always supported, we can improve the performance of third-party extensions by allowing them to access the attached thread and interpreter states directly.
StanFromIreland pushed a commit to StanFromIreland/cpython that referenced this pull request Dec 6, 2025
@ZeroIntensity @StanFromIreland
pythongh-140544: Always assume that thread locals are available (pyth…
32d3fc5
…onGH-140690)

Python has required thread local support since 3.12 (see pythonGH-103324). By assuming that thread locals are always supported, we can improve the performance of third-party extensions by allowing them to access the attached thread and interpreter states directly.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@vstinner vstinner vstinner left review comments
@markshannon markshannon Awaiting requested review from markshannon

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Uh oh!

There was an error while loading. Please reload this page.

4 participants

@ericsnowcurrently @bedevere-bot @markshannon @vstinner
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.