Hi there!
@thomas mentioned me so I tried to put together some thoughts.
First off, it looks like a ton of work went into this PEP and the PoC. Personally, I’m super excited to have the opportunity to discuss JITs in depth on a forum like this. So many thanks for all the work and for creating the opportunity to discuss it.
I agree with @thomas and @markshannon that this seems like two PEPs. I think that may be more a product of fulfilling what was being asked by the SC in the original announcement than any kind of oversight on your part. But now that it’s here, it seems like we would really want acceptance/rollback criteria to be defined in one PEP, especially if other JIT PEPs are on their way. From my own perspective, I would definitely want to see a few additional things either fully fleshed out or at least mentioned:
-
Most of the PEP talks about performance, which is great, but it seems to exclusively use geomean across platforms. I think there might be some missing discussion around the floor of performance. While it’s great to see a huge geomean, I worry more about the possibility of regressing whole classes of workloads.
-
As it stands the only real mention I see of memory is “may initially use more memory”. In my experience, method JITs in particular require more book-keeping (“Recording extra type profiling information”, “Path splitting”), holding invariants, pointers to ICs, previous compiled versions and code lifetime management (“Respecializing instructions”), etc. I would hope to see budgets for overall binary size, generated code size, and metadata memory size loosely discussed (even if the discussion is just “there will be a setting for this”).
-
I agree with @markshannon that compile latency/warmup is largely solved by the fact that you’ve chosen copy-and-patch, but I still think it should be explicitly called out outside the rejected ideas section of the PEP.
-
As mentioned by others, I would hope to see something like a real workload suite, not just pyperformance. At Meta we are struggling with this as well, as getting representative workloads is a bit of a sisyphean task. Nevertheless, I think it would be helpful to cover this even if the answer is “a small todo django app that we made”.
-
You already mentioned CET/BTI as per the Fedora request, so that’s good.
I realize this is a lot to ask, and maybe shouldn’t even have to be completed by the authors of this PEP specifically. But if there’s going to be acceptance criteria for a JIT within Python, I would hope to see most of those things covered.
Beyond the acceptance criteria, there are a couple of things I worry about with the direction this PEP is going in. I’ll go into detail below.
In my opinion, the PEP very much understates the sizable difference between a tracing JIT and a method-at-a-time JIT. It mentions “nothing else is changing from CPython 3.15” and that the middle-end “just needs to support merging type information at control-flow merge points”. I think this is glossing over a couple of important details. (Not to mention that the current incomplete PoC is already a 10K LoC change.)
A tracing JIT naturally captures the hot path, allows multiple entry points into a function by entering at back edges, and easily maintains branch history. That all must be replaced by value and control-flow merges, deopts, guard placement, and likely quite a few improvements to ICs. That’s not even to mention OSR (or some other explicit multi-entry/resume-entry design), which I would expect to become necessary for avoiding regressions on long-running loops because you’re replacing a tracing frontend and you will eventually need to support generators and coroutines. We’ve hit this problem with pyperformance and CinderX because a lot of the benchmarks are long-running loops.
The PEP mentions “Most optimizations can be implemented as local rewrites”. It’s totally possible I’m misreading this, or have a different definition of “rewrite”. But if I understand the intention correctly, I think this is contradictory to the other promised wins. Things like devirtualizing calls, lock removal, handling generators/coroutines are all outside of what I would say is “local”, and require having much more available context like control-flow and lifetime reasoning. Again, totally possible this is just a linguistic thing.
“The JIT can recover all of free-threading’s single-threaded performance losses and maybe even more.” I think this presumes the JIT will be able to eliminate many more locks and refcount operations than is actually possible. In CinderX we have a method-at-a-time JIT, and we still have a ton of these kinds of operations. It’s totally possible you can come up with some more clever optimizations than we have and eliminate some more, but from our own experience I can tell you I would be surprised if the JIT can eliminate most of them.
If I understand the PEP correctly, most of the promised performance gains are planned to be gained by the frontend change. As the PEP states multiple times in multiple ways, nothing else is changing. I don’t see how both of these things can be true. The backend is still going to be the hard limiting factor regardless. As I understand it, values across uops are limited to a small top-of-stack _PyStackRef cache rather than a general register-allocated/unboxed representation. The original copy-and-patch authors admit in their own paper “we conclude that copy-and-patch replaces the role of baseline compilers” and “we believe copy-and-patch can also be used as a fast profiling tier”. My concern is that this PEP lays out many person-years of work to change the frontend of a compiler architecture that explicitly states in its design that it is designed to be a baseline compiler, and then rejects a higher-tier JIT.
As a final point, I worry about the PEP’s focus on simplicity as a virtue of the JIT. In particular there are two quotes that stick out: “A single maintainer should be able to “fit” the entire system in their head” and “the JIT must remain accessible for contributors who do not work on it”. I’m uneasy with this framing. JITs and dynamic language runtimes are some of the more complex pieces of software out there. I promise I’m sympathetic to having too little energy and contributors, and it makes sense that you would want the things you mention. But I worry that eschewing complexity in favor of making a JIT compiler “fit” in someone’s head ultimately results in a JIT compiler not worth having.
Again, thank you very much for your work and thoughtfulness in putting together this PEP. I hope these comments are helpful in driving the conversation toward what some good acceptance criteria might look like and helping frame the discussion around the technical direction of this JIT in particular.