2 ms·
There's some subtlety here: 1. Exceptions have very high performance costs (equivalent to a longjmp which is very slow), so if you expect to have exceptional c
by pclmulqdq 4y ago
There's some subtlety here:
1. Exceptions have very high performance costs (equivalent to a longjmp which is very slow), so if you expect to have exceptional cases, it's probably a lot more efficient to not use exceptions.
2. Exceptions break the linear flow of the code when you read it, so now you have to read a lot more code to figure out what the exception paths are and where and how they are handled.
- planede 4y ago> Exceptions have very high performance costs On the sad path. On the happy path they are faster than explicit error checking.
- pclmulqdq 4y agoKind of yes, and kind of no. On the happy path, if errors are very very rare, the check is also basically free thanks to branch prediction. They start to cost something when you start to add a higher frequency of errors, which incidentally is where exceptions cost a lot more.
- mike_hearn 4y agoThe cost is higher because of all the branches that are scattered everywhere to check return codes. With exceptions there's a check at the place the error is thrown, but that's inevitable. There aren't checks scattered throughout the rest of the code, which would otherwise reduce icache utilization.
- pclmulqdq 4y agoArguing about icache utilization is a little silly here - the code will be laid out for you as though the branches are not taken (or you should force it to do so). In that case, the only "waste" of icache is the CMP and JMP, an additional 4-8 bytes per return, and literally 0 cycles. When you do take an error, each RET takes you 1 cycle, plus the 10-15 cycle mispredict for the CMP+JMP because there's a stack engine in the CPU that tells you the address to return to. It's counterintuitive that doing "a lot" of things is cheaper than doing fewer things, but it's true. In comparison, an exception involves taking the one control flow break to some cold control code (maybe page faulting), figuring out where to go using a jump table (slow), restoring the old state from that context (slow), figuring out the type of the thrown object (in many languages, also slow), and then handling accordingly. Each of these steps can easily take 100+ cycles, and may be more. The math does not work out in favor of exceptions. Neither do the benchmarks in most cases. You do 1 slow thing to avoid doing 20 things that are trivially fast.
- mike_hearn 4y agoThe checks you're talking about are duplicated more or less per statement in some types of code. Every single call site ends up with an `if err != nil` or moral equivalent. It can add up, also consider the extra register pressure. The return values aren't valuable anymore, they're just error signalling. The compiler doesn't necessarily know what your error types are, it can try to use heuristics to move those blocks around but it's not like an exception where the types are a part of the language and the compiler can know that. We're talking about startup code here, nobody will be annotating their error branches with manual predictor probabilities, so we're limited to what the compiler can do. Yes the act of throwing an exception is more work but it's exceptional, so doesn't matter. The slowest part is calculating the stack trace anyway and that's of huge value, which you don't get with error codes anyway.
- pclmulqdq 4y agoThere's no register pressure - TEST EAX, EAX (or CMP, EAX, $0) // JNZ $ERROR_HANDLER is the instruction sequence we're talking about. Most error types are enums where 0 = "good" and any nonzero value is not good. This is the inverse of the "null pointer check" in C. It consumes no registers and a negligible number of code bytes. There is obviously a sparsity of exceptional cases where error-handling code like this is worse than using exceptions. I would claim that it's a lot more sparse than you think. Many people use exceptions for things like "file not found" or "function failed for whatever reason," (my favorite) "timeout," or "bad input from the user." These cases are often not that exceptional!
- codethief 4y ago> Exceptions have very high performance costs (equivalent to a longjmp which is very slow) Could you elaborate on why they are so slow, compared to passing around/returning error objects explicitly?
- pclmulqdq 4y agoThey are slow because you need to restore context from an unknown/unpredictable place in the code, you have a table lookup (from a very cold table) to get the next program counter value, and you have to save and restore register values, while the callstack and the calling convention handle all of that complexity for you if you don't break the natural flow of the program.
- codethief 4y agoBut couldn't one implement exceptions internally by returning error codes? Yes, this would change the ABI but as long as we're not talking about the interface of a library, i.e. are not leaving the realms of our source code, this should be ok, shouldn't it? In a sense, try/catch would then just be syntactic sugar that frees you from manually checking for errors after every single function call. Instead, you just handle them in bulk in a catch block, potentially a couple stack frames further upstairs. EDIT: I just realized my suggestion wouldn't exactly be equivalent to exceptions, in the sense that it wouldn't give stack traces but error return traces, like in Zig: https://ziglang.org/documentation/master/#Error-Return-Traces https://ziglang.org/documentation/master/#Error-Return-Trace...
- codethief 4y ago> In a sense, try/catch would then just be syntactic sugar that frees you from manually checking for errors after every single function call. Instead, you just handle them in bulk in a catch block, potentially a couple stack frames further upstairs. Here someone else had the same idea: https://news.ycombinator.com/item?id=34846550 https://news.ycombinator.com/item?id=34846550
- gpderetta 4y ago1. Exceptions as commonly implemented in C++ have high overhead in the exception path. But that's just an implementation strategy. There is no reason why it wouldn't be possible to generate exactly the same code as for an optional type if desired (and in fact it was proposed for c++ cf. Herbceptions). 2. So do returns, but we have long settled that SESE is undesirable.