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 our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Design discussion for https://github.com/python/cpython/pull/114059 #648
Comments
|
(What's the DSL generator?) |
Sorry I meant to write the DSL's abstract interpreter generator. |
|
Latest performance result, pure uops, no JIT: |
|
It's possible that my comment in the PR about extensibility belongs here. |
|
I'd like to explore an idea for simplifying the code generation a bit. See here for reference. Currently, when we do constant evaluation for e.g. a binary add operation, we replace (I'm writing Looking at My first question is, when are we unable to optimize the I don't want to work out a formal proof, but I'm pretty sure that we can prove that the only way we can have a constant on the stack is if it was pushed by one of the following:
Since the first two categories are considered zappable, they are fine. However, (Alas, it currently isn't, because So what other things might intervene that make the sequence non-zappable? Well, there could be a non-constant That's too bad, because looking at that I can see that it ought to be simplifyable to Hm, so all my idea boils down to is that we can probably move the (What I had hoped to arrive at was a proof that we will never need more output space than input space, and that we can do this on the fly so we won't need a separate output write buffer. But at least until the walrus example is solved, we can't.) |
Why would we not merge that? It seems to be an integral part of the PR. |
The alternative is Mark's version here https://github.com/faster-cpython/cpython/blob/8aad399240928a7dfe548c0154d7354c96a1f6ff/Python/tier2_specializer.c I plan to eventually integrate something like that though. |
|
The file you linked to would just be another input to the generator though. You'd still need a generator. All it saves you is about 150 lines in optimizer_analysis.c, at the cost of some more work in the generator. One possible advantage, because the new file is specific to the symbolic interpreter part, you could move the stack effect annotations there and possibly invent some annotations that are more suitable to that use case (but less proper when describing opcodes in general). |
I want to separate out high-level discussion from that PR to this issue. The PR comment chain should henceforth mostly be for discussion of the implementation. My reasoning is that seeing both mixed with commits makes things hard to follow.
I want to summarise my thoughts below. This is in response to @markshannon's comment at python/cpython#114059 (comment)
Addressing the current concerns
Performance
For some reason that I have not yet determined, the run in that PR has the optimizer failing more often than expected. The latest run suggests a 1% speedup, especially benefiting numeric code. My own local benchmarks suggest 0-4% speedup on default pyperformance (the benchmark runner here is a little different because it pulls pyston benchmarks as well).
A lack of generality / Abstract Interpreter DSL plans
There are concerns with whether the abstract interpreter generated in that PR is too specific to a single use case. If you want a comparison, Mark wrote a proof-of-concept, but not-yet-working abstract interpreter DSL here at his branch.
The future plan is to have a similar sort of DSL to what Mark has for rewrite rules and peephole optimizations on uop sequences. It is elegant and IMO a very clean way of expressing rules.
My current plan however is to keep the current abstract interpreter in the PR as-is, and then in the future, augment the abstract interpreter cases with the DSL. I have a very simple reason: the current abstract interpreter represents what can be inferred automatically from the bytecodes definition (apart from the type annotations). It reduces the amount of DSL we have to maintain. It does not make sense to write DSL rules for constant evaluation when something like that can be inferred automatically (by definition of pure op). The vast majority of bytecode follow simple rules that do not need an abstract interpreter DSL to define.
That said, I do definitely want to adopt Mark's approach when optimizing the bytecode. In short, I want to combine both approaches and maximize what I can automatically infer, and anything else leave to DSL rules for optimization.
This also answers the concerns for extensibility.
What does the PR actually offer?
Assuming we don't merge the current DSL's abstract interpreter generator.
Data structures
The core thing is that it sets up the data structures required for an abstract interpreter. I cannot oversell this enough. This is subtle, but the abstract interpreter data structure itself is the reduced version of the full optimizer I originally wrote. I made various design decisions to make optimization simpler with the benefit of hindsight (and 3 rewrites :). .For example, its state already models inlined calls. This is why it uses a single contiguous stack to share separate frames's localsplus, so that calculation for inlined locals is made simple. A lot of experimentation has been put into the infrastructure (and admittedly less so the DSL, which has much room for improvement as mentioned above).
Type propagation
The next thing is that it offers an effective type analyzer. On the latest branch,
_GUARD_BOTH_INTis cut by over 70%. Mark has said it's not an instruction we should focus optimization on. I fully agree. I am purely using its statistics as a proxy to see how effective our type propagation is.Poor man's loop invariant code motion for guards
I called this loop peeling originally, but it gave the wrong idea. The main reason why I repeat the body of a loop a second time is because during the first round, types are not yet known by the type analyzer. Consider this:
Running the type propagator one more time and duplicating the body gives this code
Note: the main loop is now completely guard-free! This is the benefit of "peeling" the loop. It's a poor man's loop invariant code motion for guards.
This doubled the number of float and int guards we reduce at runtime, from roughly 15% to roughly 30%, and roughly 35% to roughly 70% respectively.
The text was updated successfully, but these errors were encountered: