
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.
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. 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). 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). 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.
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.
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. [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").
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 :) 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.
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.
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. Cheers, Steve