digitalmars.D - Raymond Chen's take on so-called zero cost exceptions
- Walter Bright (11/11) Feb 28 2022 "The presence of exceptions means that the code generation is subject to...
- H. S. Teoh (18/32) Feb 28 2022 How is this any worse than explicitly checking for error codes? Isn't
- deadalnix (4/19) Feb 28 2022 obj can be kept in a register in the goto case, it cannot in the
- Elronnd (13/16) Feb 28 2022 Nope, it can be kept in a register in the exception case too.
- Guillaume Piolat (11/14) Feb 28 2022 In my C++ years, exceptions in combination with RAII were the
- H. S. Teoh (48/61) Feb 28 2022 IIRC, we had this discussion before some time ago, and Adam pointed out
- Araq (5/9) Feb 28 2022 Exactly. It's entirely feasible. And it is what Swift does:
- deadalnix (9/18) Mar 01 2022 Yes, this is doable and indeed done in some cases. The main
- Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= (20/25) Mar 01 2022 Yes, that is my experience as well. You try to avoid the need for
- deadalnix (11/25) Feb 28 2022 There are a bit more to this though. First the exception ABI on
- Adam D Ruppe (2/4) Feb 28 2022
- forkit (13/14) Feb 28 2022 Yeah, well, you can switch gears more quickly by having paddles
- meta (15/31) Feb 28 2022 D is in a position where it can, and should, do what ever it
- forkit (3/6) Feb 28 2022 Lucky you are not my mechanic. Otherwise, you would have just
- meta (12/18) Mar 01 2022 It's ok, customers aren't eternal, we need to refresh the pool of
- forkit (9/10) Feb 28 2022 We need to refocus on why exception handling was introduced, and
- IGotD- (8/10) Mar 01 2022 Yes, features always comes at a cost. Bounds checking costs CPU
- Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= (8/13) Mar 01 2022 I think it has more to do with microbenchmarking between
- meta (24/36) Mar 01 2022 You can disable bounds checking, and it's not the same, you know
- Guillaume Piolat (5/9) Mar 01 2022 +1000
"The presence of exceptions means that the code generation is subject to constraints that don’t show up explicitly in the code generation: Before performing any operation that could potentially throw an exception, the compiler must spill any object state back into memory if the object is observable from an exception handler. (Any object with a destructor is observable, since the exception handler may have to run the destructor.) Similarly, potentially-throwing operations limit the compiler’s ability to reorder or eliminate loads from or stores to observable objects because the exception removes the guarantee of mainline execution." https://devblogs.microsoft.com/oldnewthing/20220228-00/?p=106296 This is what I've been saying.
Feb 28 2022
On Mon, Feb 28, 2022 at 12:33:17PM -0800, Walter Bright via Digitalmars-d wrote:"The presence of exceptions means that the code generation is subject to constraints that don’t show up explicitly in the code generation: Before performing any operation that could potentially throw an exception, the compiler must spill any object state back into memory if the object is observable from an exception handler. (Any object with a destructor is observable, since the exception handler may have to run the destructor.) Similarly, potentially-throwing operations limit the compiler’s ability to reorder or eliminate loads from or stores to observable objects because the exception removes the guarantee of mainline execution." https://devblogs.microsoft.com/oldnewthing/20220228-00/?p=106296 This is what I've been saying.How is this any worse than explicitly checking for error codes? Isn't the optimization situation of: MyObj obj; mayThrow(); ... // obj.dtor called different from: MyObj obj; if (mayError() == ERROR) goto END; ... END: // obj.dtor called ? T -- Computers aren't intelligent; they only think they are.
Feb 28 2022
On Monday, 28 February 2022 at 21:39:03 UTC, H. S. Teoh wrote:How is this any worse than explicitly checking for error codes? Isn't the optimization situation of: MyObj obj; mayThrow(); ... // obj.dtor called different from: MyObj obj; if (mayError() == ERROR) goto END; ... END: // obj.dtor called ? Tobj can be kept in a register in the goto case, it cannot in the exception case. So you'll have a couple extra load/store vs extra branches.
Feb 28 2022
On Tuesday, 1 March 2022 at 01:39:05 UTC, deadalnix wrote:obj can be kept in a register in the goto case, it cannot in the exception case. So you'll have a couple extra load/store vs extra branches.Nope, it can be kept in a register in the exception case too. See: https://godbolt.org/z/zP1P3xvr3 The pushes and pops of RBX are necessary in both cases, because it is a caller-saved register. (And, well, they are also necessary for stack alignment, so be wary of taking too many conclusions from a microbenchmark.) Beyond that, note that the exception-using versions have fewer instructions in the hot path, fewer branches, and exactly the same number of memory accesses as the manually-checking versions. (GCC generates better code, but clang's implementation of 'h' is more representative, which is why I show both; *usually* you can't run both the happy path and the sad path branchlessly.)
Feb 28 2022
On Tuesday, 1 March 2022 at 05:06:22 UTC, Elronnd wrote:The pushes and pops of RBX are necessary in both cases, because it is a caller-saved registercallee-saved, of course.
Feb 28 2022
On Tuesday, 1 March 2022 at 05:06:22 UTC, Elronnd wrote:On Tuesday, 1 March 2022 at 01:39:05 UTC, deadalnix wrote:I'm not sure that gcc's code is actually better, it's pretty much the same, with the cold section split out. The only notable difference in is to use add/mov vs lea for gcc. Interestingly, i submitted a patch to split exception code in cold section for LLVM the way GCC does it, but it ended up not being merged :/ In any case, your example show that even in the presence of exception, register promotion is still possible, which I think strengthen my original point: of all the solutions available, exception are the one that imposes the smallest cost on the non exception path.obj can be kept in a register in the goto case, it cannot in the exception case. So you'll have a couple extra load/store vs extra branches.Nope, it can be kept in a register in the exception case too. See: https://godbolt.org/z/zP1P3xvr3 The pushes and pops of RBX are necessary in both cases, because it is a caller-saved register. (And, well, they are also necessary for stack alignment, so be wary of taking too many conclusions from a microbenchmark.) Beyond that, note that the exception-using versions have fewer instructions in the hot path, fewer branches, and exactly the same number of memory accesses as the manually-checking versions. (GCC generates better code, but clang's implementation of 'h' is more representative, which is why I show both; *usually* you can't run both the happy path and the sad path branchlessly.)
Mar 01 2022
On Tuesday, 1 March 2022 at 12:51:08 UTC, deadalnix wrote:I'm not sure that gcc's code is actually better, it's pretty much the same, with the cold section split out. The only notable difference in is to use add/mov vs lea for gcc.Yes: 1. hot/cold splitting 2. lea vs add/mov (marginal, but still smaller) 3. branchless h I think all of these qualify gcc's code as better.Interestingly, i submitted a patch to split exception code in cold section for LLVM the way GCC does it, but it ended up not being merged :/Curious, why not?
Mar 01 2022
On Tuesday, 1 March 2022 at 19:20:04 UTC, Elronnd wrote:The perf benefits weren't so obvious expect for huge binaries. Huge binaries ended up being optimizable in other ways, such as bolt, PGO+LTO and the benefit wasn't so obvious in the end.Interestingly, i submitted a patch to split exception code in cold section for LLVM the way GCC does it, but it ended up not being merged :/Curious, why not?
Mar 01 2022
On Monday, 28 February 2022 at 20:33:17 UTC, Walter Bright wrote:"The presence of exceptions means that the code generation is subject to constraints that don’t show up explicitly in the code generationIn my C++ years, exceptions in combination with RAII were the best barrier of defense against leaks and bugs in error paths. Also they would allow C++ constructors to fail, and error codes didn't. Worse, with error codes, people routinely conflated runtime errors and unrecoverable errors. Performance is imo a false concern here since 1. either code that should be fast is devoid of eror handling in the first place 2. correctness of whole codebases is at stake 3. you can always make a correct program faster, but there will be noone to realize the incorrect program is incorrect.
Feb 28 2022
On Mon, Feb 28, 2022 at 11:01:46PM +0000, Guillaume Piolat via Digitalmars-d wrote:On Monday, 28 February 2022 at 20:33:17 UTC, Walter Bright wrote:IIRC, we had this discussion before some time ago, and Adam pointed out that in D's early days it used to have its own exception-handling implementation that involved passing exception status via a spare register, supposedly faster than the baroque C++-style libunwind / (whatever the Windows equivalent is) implementation. But later on, in the name of C++ compatibility, that got replaced with libunwind / Windows EH. My point is, if you're dealing with code that may fail, you've got to handle the error case *somehow*. Whether it's via a return code, or libunwind, or passing some status in a spare register that the compiler automatically inserts a check for. The syntax is really irrelevant. Whether you write: void mayFail() { throw new Exception(""); } void myFunc() { MyObj obj; mayFail(); ... // obj.dtor invoked } or: int mayFail() { return ERROR; } void myFunc() { MyObj obj; if (mayFail() == ERROR) // returns error status goto EXIT; ... EXIT: // obj.dtor invoked } the semantics are essentially the same. If one implementation of exceptions is not as performant, why can't we switch to a more efficient implementation? After all, the compiler can, in theory, implement `throw` in the first code snippet above by lowering it into the equivalent of the second code snippet. Say, by using a spare register to indicate the error (if mayFail returns a value besides its error status). The actual implementation of how exceptions are handled isn't really tied to the surface syntax of the program. One way or another you need to handle the error condition somehow; what about exceptions makes it fundamentally less efficient than handling an error code? If error codes are somehow fundamentally more efficient, why can't the compiler just rewrite throwing functions into functions that return error codes, with the compiler inserting error code checks into the caller as needed? T -- Why waste time learning, when ignorance is instantaneous? -- Hobbes, from Calvin & Hobbes"The presence of exceptions means that the code generation is subject to constraints that don’t show up explicitly in the code generationIn my C++ years, exceptions in combination with RAII were the best barrier of defense against leaks and bugs in error paths. Also they would allow C++ constructors to fail, and error codes didn't. Worse, with error codes, people routinely conflated runtime errors and unrecoverable errors. Performance is imo a false concern here since 1. either code that should be fast is devoid of eror handling in the first place 2. correctness of whole codebases is at stake 3. you can always make a correct program faster, but there will be noone to realize the incorrect program is incorrect.
Feb 28 2022
On Tuesday, 1 March 2022 at 06:44:05 UTC, H. S. Teoh wrote:If error codes are somehow fundamentally more efficient, why can't the compiler just rewrite throwing functions into functions that return error codes, with the compiler inserting error code checks into the caller as needed?Exactly. It's entirely feasible. And it is what Swift does: https://www.mikeash.com/pyblog/friday-qa-2017-08-25-swift-error-handling-implementation.html (It's not universally faster than table based exceptions but it seems preferable for embedded devices.)
Feb 28 2022
On Tuesday, 1 March 2022 at 06:50:54 UTC, Araq wrote:On Tuesday, 1 March 2022 at 06:44:05 UTC, H. S. Teoh wrote:Yes, this is doable and indeed done in some cases. The main benefit is that the resulting binaries are smaller, which is important on embeded devices. Exceptions handling typically causes a ~20% increase of the binaries. Another benefit is that it is much cheaper in the exceptional case. The flip side of this is that it imposes a greater cost in the non throwing case.If error codes are somehow fundamentally more efficient, why can't the compiler just rewrite throwing functions into functions that return error codes, with the compiler inserting error code checks into the caller as needed?Exactly. It's entirely feasible. And it is what Swift does: https://www.mikeash.com/pyblog/friday-qa-2017-08-25-swift-error-handling-implementation.html (It's not universally faster than table based exceptions but it seems preferable for embedded devices.)
Mar 01 2022
On Monday, 28 February 2022 at 23:01:46 UTC, Guillaume Piolat wrote:Performance is imo a false concern here since 1. either code that should be fast is devoid of eror handling in the first place 2. correctness of whole codebases is at stake 3. you can always make a correct program faster, but there will be noone to realize the incorrect program is incorrect.Yes, that is my experience as well. You try to avoid the need for error-handling and if errors can arise you delay the handling of it. Examples: - In a parser you can build a log, then check the log after parsing. - In a raytracer you can tag a pixel as failed and check it at the bottom level and retry with better solvers (or a slightly different angle or whatever). Complex error situations usually are most relevant in I/O, but in order to get the highest performance I/O you have to special case all the I/O code to the platform and use whatever response mechanism the platform provides. If you are creating a library abstraction you won't get optimal performance anyway and exceptions tend to have smaller impact than other sources for latency. I probably would not want to use unchecked exceptions for embedded, but that is a different discussion.
Mar 01 2022
On Monday, 28 February 2022 at 20:33:17 UTC, Walter Bright wrote:"The presence of exceptions means that the code generation is subject to constraints that don’t show up explicitly in the code generation: Before performing any operation that could potentially throw an exception, the compiler must spill any object state back into memory if the object is observable from an exception handler. (Any object with a destructor is observable, since the exception handler may have to run the destructor.) Similarly, potentially-throwing operations limit the compiler’s ability to reorder or eliminate loads from or stores to observable objects because the exception removes the guarantee of mainline execution." https://devblogs.microsoft.com/oldnewthing/20220228-00/?p=106296 This is what I've been saying.There are a bit more to this though. First the exception ABI on windows is 100% atrocious and impose way more constraints than the usual libunwind based ones. But most importantly, second, the argument is fallacious, as comparing code with and without exception makes no sense. What you want to compare is the code that uses exception vs the code that uses *something else* for error handling. While that something else is almost always more expensive than exception on the non throwing path. The numbers from the original paper show this.
Feb 28 2022
On Monday, 28 February 2022 at 20:33:17 UTC, Walter Bright wrote:https://devblogs.microsoft.com/oldnewthing/20220228-00/?p=106296"Zero-cost exceptions are great"This is what I've been saying.
Feb 28 2022
On Monday, 28 February 2022 at 20:33:17 UTC, Walter Bright wrote:This is what I've been saying.Yeah, well, you can switch gears more quickly by having paddles shifters on the steering wheel. But so many of us have no problem with the location of the gear selector that we're all familiar with. I feel like my mechanic is telling me I should move to paddle shifters, cause then I can change more quickly. But what's it gonna cost me? And can I both, in case I don't like (or can't get used to) the paddle shifters. So the point being made in the article, is kinda... pointless.. really. i.e. It's the other questions that immediately arise that need to be answered (or even asked would be a nice start).
Feb 28 2022
On Tuesday, 1 March 2022 at 06:57:26 UTC, forkit wrote:On Monday, 28 February 2022 at 20:33:17 UTC, Walter Bright wrote:D is in a position where it can, and should, do what ever it wants to do, that gives us an advantage over the competition, Nim for example didn't wait what ever someone says on a forum to implement RC, Zig didn't wait for anybody to do the way they do error handling, and soon compile time error for unused things despite many people against it [0] Nobody should wait for you to become comfortable to improve things People attach to a language for its vision, not because it sticks to whatever he though was good 20 years ago Someone mentioned Swift, they didn't wait for anybody to implement the Actor model at language level, same for adding language features to make SwiftUI play nice We try, we make mistakes, we learn, we change, we teach, we evolve [0] https://github.com/ziglang/zig/issues/335This is what I've been saying.Yeah, well, you can switch gears more quickly by having paddles shifters on the steering wheel. But so many of us have no problem with the location of the gear selector that we're all familiar with. I feel like my mechanic is telling me I should move to paddle shifters, cause then I can change more quickly. But what's it gonna cost me? And can I both, in case I don't like (or can't get used to) the paddle shifters. So the point being made in the article, is kinda... pointless.. really. i.e. It's the other questions that immediately arise that need to be answered (or even asked would be a nice start).
Feb 28 2022
On Tuesday, 1 March 2022 at 07:08:24 UTC, meta wrote:... Nobody should wait for you to become comfortable to improve things ....Lucky you are not my mechanic. Otherwise, you would have just lost a customer ;-)
Feb 28 2022
On Tuesday, 1 March 2022 at 07:18:29 UTC, forkit wrote:On Tuesday, 1 March 2022 at 07:08:24 UTC, meta wrote:It's ok, customers aren't eternal, we need to refresh the pool of developers and attract new souls, whom find exception handling to be a bad idea ;) However we also need to retain existing ones It's hard to balance the two, but the former is important, it makes it so the product last when the creator is no longer here.. it's life That's how my company still makes that mobile game as a service, the same game, since the beginning of iPhones, still kicking strong with new users, the game is totally different today than what it was in the past.. totally worth it... Nobody should wait for you to become comfortable to improve things ....Lucky you are not my mechanic. Otherwise, you would have just lost a customer ;-)
Mar 01 2022
On Tuesday, 1 March 2022 at 06:57:26 UTC, forkit wrote:On Monday, 28 February 2022 at 20:33:17 UTC, Walter BrightWe need to refocus on why exception handling was introduced, and not just on it's 'cost'. Only then do we have a subject worthy of discussion: "..exception handling was introduced to solve some of these problems" - CppCon 2019: Ben Saks “Back to Basics: Exception Handling and Exception Safety” https://www.youtube.com/watch?v=W6jZKibuJpU
Feb 28 2022
On Tuesday, 1 March 2022 at 07:50:27 UTC, forkit wrote:We need to refocus on why exception handling was introduced, and not just on it's 'cost'.Yes, features always comes at a cost. Bounds checking costs CPU cycles but most people are OK with the extra cost. Another thing that I don't understand. Exceptions have been around for a long time. In the 90s and beginning of 2000s there wasn't much talk about the cost of exceptions. 20 years later and computers are a magnitude faster, suddenly exceptions are too expensive.
Mar 01 2022
On Tuesday, 1 March 2022 at 10:27:10 UTC, IGotD- wrote:Another thing that I don't understand. Exceptions have been around for a long time. In the 90s and beginning of 2000s there wasn't much talk about the cost of exceptions. 20 years later and computers are a magnitude faster, suddenly exceptions are too expensive.I think it has more to do with microbenchmarking between competing solutions when choosing a language for a project. In some cases you can get better performance with one solution in comparison to another. I guess it also could matter if you transpile to C++ from other languages as that can lead to "dumb" code that no proficient C++ programmer would write.
Mar 01 2022
On Tuesday, 1 March 2022 at 10:27:10 UTC, IGotD- wrote:On Tuesday, 1 March 2022 at 07:50:27 UTC, forkit wrote:You can disable bounds checking, and it's not the same, you know before hand the cost and how it affects your program as a whole, which is not the case for exception handling It also is a way to design your software, proper error handling is verbose but leads to better and more portable results, syntax is simpler, easier to read and to follow With exception handling, you never know what will throw, or if something was already catched, or if you catch or cast the wrong Base type, etc (thanks OOP for yet another level of complexity btw) Error codes are just better in every aspects, scales from embedded to what ever complex solution you have They are even better when the language understands what is an 'error' at the semantic level Again, error handling is better in every aspects Also my use cases for D are system level softwares (graphics/audio engine, low level networking, automation) I should mention that I'm not asking for enforcing error handling to everyone, I just want to make sure I can live and keep programming by not having to use exceptions at all :) Also It is a personal opinion, you can't make me use them if I consider it to be a bad practice, even if some people disagree with that statementWe need to refocus on why exception handling was introduced, and not just on it's 'cost'.Yes, features always comes at a cost. Bounds checking costs CPU cycles but most people are OK with the extra cost. Another thing that I don't understand. Exceptions have been around for a long time. In the 90s and beginning of 2000s there wasn't much talk about the cost of exceptions. 20 years later and computers are a magnitude faster, suddenly exceptions are too expensive.
Mar 01 2022
I should also mention that I work a lot with C/C++ code base, we disabled exceptions because the C++ code inherited a C codebase, we found that simple error code works better for us, so we didn't bother using what C++ had to offer, hence my stance against exception handling, it's just better, simpler..
Mar 01 2022
On Tuesday, 1 March 2022 at 12:13:51 UTC, meta wrote:... Also my use cases for D are system level softwares (graphics/audio engine, low level networking, automation) I should mention that I'm not asking for enforcing error handling to everyone, I just want to make sure I can live and keep programming by not having to use exceptions at all :) ...but you can already do this (i.e. program by not having to use exceptions at all). dmd, for example, doesn't use exceptions (as per previous post from Walter). And it's in D. I presume you're talking about exceptions in phobos? I'm not sure how much of phobos was designed for such low-level programming. But in any case, it's all open-source, and can be modified (or used to build your own libraries). So there is nothing holding you back ;-)
Mar 01 2022
On Tuesday, 1 March 2022 at 07:50:27 UTC, forkit wrote:On Tuesday, 1 March 2022 at 06:57:26 UTC, forkit wrote:+1000 The counter-hype of being anti-exceptions is even worse than being anti-OOP... 10% facts and 90% cyclical hypeOn Monday, 28 February 2022 at 20:33:17 UTC, Walter BrightWe need to refocus on why exception handling was introduced, and not just on it's 'cost'.
Mar 01 2022