On 17/03/2020 3:38 pm, Steve Dower wrote:
On 17Mar2020 1447, Mark Shannon wrote:
On 16/03/2020 3:04 pm, Victor Stinner wrote:
In short, the answer is yes.
I said "no" then and gave reasons. AFAICT no one has faulted my
reasoning.
I said "yes" then and was also not faulted.
I'll do that now then ;)
The accessibility of a thread-local variable is a strict superset of
that of a function-local variable.
Therefore storing the thread state in a thread-local variable is at
least as capable as passing thread-state as a parameter.
Let me reiterate why using a thread-local variable is better than
passing the thread state down the C stack.
1. Using a thread-local variable for the thread state requires much
smaller changes to the code base.
Using thread-local variables enforces a threading model on the host
application, rather than working with the existing threading model. So
anyone embedding is forced into *significantly* more code as a result.
Putting a value in a function-local variable enforces stronger
restrictions than putting it in a thread-local variable.
I am proposing that we *don't* change the API. How does that make more
work for anyone using the API?
We can (and should) maintain a public-facing API that uses TLS to pass
the current thread state around - we have compatibility constraints. But
we can also add private versions that take the thread state (once you've
started trying/struggling to really embed CPython, you'll happily take a
dependency on "private" APIs).
Again, I am requesting that we *don't* change the API.
Not changing the API maintains backwards compatibility better than
changing it, surely.
If the only available API requires TLS, then you're likely to see the
caller wrap it all up in a function that updates TLS before calling. Or
alternatively, introduce dedicated threads for running Python snippets
on, and all the (dead)locking that results (yes, I've done both).
All the platforms that we support have thread-local storage.
If a platform doesn't have threads at all, then thread-local just
degenerates to a global.
Our goal as core CPython developers should be to sacrifice our own
effort to reduce the effort needed by our users, not to do things that
make our own lives easier but harm them.
Indeed. We might want to speed Python up a bit as well :)
2. Using a thread-local variable is less error prone. When passing
tstate as a parameter, what happens if the tstate argument is from a
different thread or is NULL? Are you adding checks for those cases?
What are the performance implications of adding those checks?
Undefined behaviour is totally acceptable here. We can assert in debug
builds - developers who make use of this can test with debug builds.
I'm not sure what your point is about undefined behaviour.
3. Using a thread-local variable is likely to be a little bit faster.
Passing an argument down the stack increases register pressure and
spills.
Accessing a thread-local is slower at the point of access, but the
cost is incurred only when it is needed, so is cheaper overall.
Compilers can optimise parameters/locals in ways that are far more
efficient than they can do for anything stored outside the call stack.
Especially for internal calls. Going through public/exported functions
is a little more restricted in terms of optimisations, but if we
identify an issue here then we can work on that then.
Please skip the patronizing "how compilers work" stuff.
I know how register allocators work.
[OTHER POST]
Just to be clear, this is what I mean by a thread local variable:
https://godbolt.org/z/dpSo-Q
Showing what one particular compiler generates for one particular
situation is terrible information (I won't bother calling it "evidence").
The particular situation is the use of a thread-local variable, which is
the point under discussion.
Here's the links for clang and MSVC:
https://godbolt.org/z/YnbbqD
https://www.godbolt.ms/z/9nQEqf
One motivation is to ease the implementation of subinterpreters (PEP
554). But PEP 554 describes more than public API than the
implementation.
I don't see how this eases the implementation of subinterpreters.
Surely it makes it harder by causing merge conflicts.
That's a very selfish point-of-view :)
Why? Merge conflicts are a problem for everyone.
It eases it because many more operations need to know the current Python
"thread" in order to access things that used to be globals, such as
PyTypeObject instances. Having the thread state easily and efficiently
accessible does make a difference here.
Indeed. I want it to be easily and efficiently accessible.
Putting it a thread-local variable does both.
For additional efficiency `_PyThreadState_GET()` can be defined as `inline`.
The long-term goal is to be able to run multiple isolated interpreters
in parallel.
An admirable goal, IMO.
But how is it to be done? That is the question.
By isolating thread states properly from global state.
Yes. But why do you think passing a value down the stack does that
better than a thread-local variable?
Sorry about that :-/ A lot of Python internals should be modified to
implement subinterpreters.
I don't think they *should*. When they *must*, then do that.
Changes should only be made if necessary for correctness or for a
significant improvement of performance.
They must - I think Victor just chose the wrong English word there.
Correctness is the first thing to fall when you access globals in
multithreaded code, and the CPython code base accesses a lot of globals.
Indeed, but this discussion has nothing to do with global variables, it
is about how we access thread-state.
Cheers,
Mark.
Cheers,
Steve
_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-le...@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at
https://mail.python.org/archives/list/python-dev@python.org/message/ERBHVDSI7OVKDZEOAU52PBFBTGA4USJN/
Code of Conduct: http://python.org/psf/codeofconduct/