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
optimise try-except for the happy path #226
Comments
wouldn't the jump (with the current code) from try-body to else-body just be moved to a jump from finally to after except-blocks? So happy path would go from
to
amount of jumps in happy path would be the same |
Once you bring in effects like inline the finally block (#221) there are more jumps. The new layout can guarantee that the happy path has only one jump. |
Also, emitting the happy path code consecutively could open up the possibility of emitting the other paths’ code somewhere else completely (end of the function?) and ending up with no jumps at all for the happy path. |
I tried moving the else block before the except block and everything seems to work except a couple of trace tests fail (one more line is traced). I'm investigating (i.e., trying to understand the trace). |
I made a PR for the else-block change. I added a couple of tests and I think it's tracing correctly now. This PR keeps us with the same number of jumps - there is a jump from the end of the else block to the block that begins right after the except handlers (the finally block if there is one). I will take care of this jump later, but note that already we gain from this in terms of code size because now there is only one exit in the happy path so the finally block doesn't get copied in order to be inlined after each exit from the else block as in (#221). And I believe this is a net gain because we never have more jumps/blocks than we did before. |
The else-block change was merged, and while looking into the finally block I noticed this:
The exception from the finally block has the exception from the except block as its context, which seems right, but the literal reading of the traceback is that the finally block is an exception handler of the except block, which is a little weird. |
(To be clear, this is how it was before the last PR was merged, it's not that the else block change broke something). |
Well, in a sense See Brett's blog post: |
The next steps here are
If we do 1 without 2 then we will reverse the gains of the else block move (we still need to jump over except, but now we inline finally, possibly copying it). So 2 should come first. Currently 2 would mean we create two lists of blocks in the compiler, hot and cold, and then at the end of code gen we set b_next of the hot list to the head of the cold list. I’m on the fence whether it’s worth doing this before the refactor . |
Now that the jumps are relative, we can reorder basic blocks. In python/cpython#91804 I marked basic blocks from inside "except" as "cold", and then added a reordering stage that pushes these blocks to the end of the function, followed by a repeat of the "remove jumps that just go to the next block" step. The result can be seen here: python/cpython#91804 (comment) |
Cool! |
Micro benchmarks show that a try-except is about 8 or 9% faster in the no-exception case (I removed a try-except from the main loop of timeit to get an accurate reading, otherwise just timeit of "x = 1" showed a difference): New: Old: |
pyperformance didn't show a difference other than noise. |
PR at python/cpython#92769. |
🎉 |
The code is currently generated as something like this:
if instead we do something like:
then there are fewer jumps in the happy path.
As a bonus, everything other than the except-blocks can be shared between try and try-star.
The text was updated successfully, but these errors were encountered: