Live data from Hacker News

Bringing Faster Exceptions to Rust

purplesyringa.moe

1–10 of 35 posts

Re: Bringing Faster Exceptions to Rust

#2
Great analysis of unwinding overhead in Rust. The framing of exceptions as "alternate returns" is enlightening - they should be cheap in theory, which makes the current ~2.3μs overhead particularly interesting to dissect. The optimization journey from removing unnecessary type erasure to using thread-locals is well explained. While the 4.3x speedup is impressive, I think the bigger takeaway isn't about replacing Result with panics, but rather identifying where non-local control flow genuinely makes sense. Looking forward to the deep dive into unwinder implementations.

Re: Bringing Faster Exceptions to Rust

#3
> Returning to an alternate address shouldn’t be significantly more expensive than returning to the default address, so this has to be cheap.

Modern CPUs add complications to arguments like this. Branches stall the execution pipeline, so branch prediction was invented to keep the pipeline flowing. Return instructions are perfectly predicted, which makes them literally free. At the very least, any alternate return scheme has to pay for a full misprediction. That can be expensive.

Re: Bringing Faster Exceptions to Rust

#4
post #3

> Returning to an alternate address shouldn’t be significantly more expensive than returning to the default address, so this has to be cheap. Modern CPUs add complications to arguments like this. Branches stall the execution pipeline, so branch prediction was invented to keep the pipeline flowing. Return instructions are perfectly predicted, which makes them literally free. At the very least, any alternate return sch…

This doesn't really make sense. The branch predictor relies on a history of previous executions of that branch, or on explicit hints, to decide if a branch will be taken or not. Based on this prediction, the speculative execution hardware then sees the jump (return/panic) and loads the code from that address into the icache. There is 0 difference between `if (condition) jump $panic_recover_address` and `if (condition) jump $function_return_address` in terms of how easy or hard it is to predict or speculatively load based on the prediction.

Re: Bringing Faster Exceptions to Rust

#5
post #3

> Returning to an alternate address shouldn’t be significantly more expensive than returning to the default address, so this has to be cheap. Modern CPUs add complications to arguments like this. Branches stall the execution pipeline, so branch prediction was invented to keep the pipeline flowing. Return instructions are perfectly predicted, which makes them literally free. At the very least, any alternate return sch…

This doesn't really make sense. The branch predictor relies on a history of previous executions of that branch, or on explicit hints, to decide if a branch will be taken or not. Based on this prediction, the speculative execution hardware then sees the jump (return/panic) and loads the code from that address into the icache. There is 0 difference between `if (condition) jump $panic_recover_address` and `if (condition…

On x86, ret and call are explicit instructions. Ret always predicts the address of the last call, which is (usually) 100% accurate. Your example of `if (condition) jump $panic_recover_address` contains two branches, either of which can be mispredicted.

Re: Bringing Faster Exceptions to Rust

#6
post #5

Earlier quoted context omitted.

This doesn't really make sense. The branch predictor relies on a history of previous executions of that branch, or on explicit hints, to decide if a branch will be taken or not. Based on this prediction, the speculative execution hardware then sees the jump (return/panic) and loads the code from that address into the icache. There is 0 difference between `if (condition) jump $panic_recover_address` and `if (condition…

On x86, ret and call are explicit instructions. Ret always predicts the address of the last call, which is (usually) 100% accurate. Your example of `if (condition) jump $panic_recover_address` contains two branches, either of which can be mispredicted.

No, an unconditional jump to a fixed address can't be mispredicted.

Re: Bringing Faster Exceptions to Rust

#7
post #5

Earlier quoted context omitted.

On x86, ret and call are explicit instructions. Ret always predicts the address of the last call, which is (usually) 100% accurate. Your example of `if (condition) jump $panic_recover_address` contains two branches, either of which can be mispredicted.

No, an unconditional jump to a fixed address can't be mispredicted.

How can a reusable function have a fixed return address?

Re: Bringing Faster Exceptions to Rust

#8
post #7

Earlier quoted context omitted.

No, an unconditional jump to a fixed address can't be mispredicted.

How can a reusable function have a fixed return address?

Because of the stack? It is not fixed, but once you a function is called, the CPU must know where it was called from.

Re: Bringing Faster Exceptions to Rust

#9
post #5

Earlier quoted context omitted.

This doesn't really make sense. The branch predictor relies on a history of previous executions of that branch, or on explicit hints, to decide if a branch will be taken or not. Based on this prediction, the speculative execution hardware then sees the jump (return/panic) and loads the code from that address into the icache. There is 0 difference between `if (condition) jump $panic_recover_address` and `if (condition…

On x86, ret and call are explicit instructions. Ret always predicts the address of the last call, which is (usually) 100% accurate. Your example of `if (condition) jump $panic_recover_address` contains two branches, either of which can be mispredicted.

Sine Intel processors have shadow (call-)stack to ensure control-flow integrity, I imagine they use it to predict the return address as well.

Re: Bringing Faster Exceptions to Rust

#10
Well, unwinding can be as simple (and as fast) as

    MOV  SP, [installed_handler]
    JMP  [installed_handler+WORD]
but it only works if you don't need to run the defers/Drop's/destructors/etc. for stuff that's on the stack between the current frame and the handler's frame. Which you do, most of the time.
Post reply on HN