6 ms·
Different languages have different exception handing optimizations. A Java version of the example can run very slow or very fast, depending on how clever you ar
by hashmash 4y ago
Different languages have different exception handing optimizations. A Java version of the example can run very slow or very fast, depending on how clever you are.
When a new RuntimeException is thrown half the time, the example runs about 650 times slower when compared to a function which adds up the integers without using exceptions. If I define an exception subclass which doesn't fill in the stack trace, then it runs about 10 times slower. If I throw a singleton exception instance, then the performance is identical.
The reason why the performance is identical is because HotSpot inlined the code and converted the immediate throw-catch into a simple goto. It wasn't smart enough to see that the stack trace wasn't needed, nor was it smart enough to see that allocating new instances wasn't needed either. I had to make those transformations manually.
- nhoughto 4y agoGreat details! Why only half the time though? What is the behavior the rest of the time?
- hashmash 4y agoI wanted to make sure all code paths were executed, and so I filled the array of ints with 50% negative values.
- User23 4y agoThanks for this short and fine example of Java optimization. I still have a lingering loathing for the language based on the 2000s era marketing, but technically speaking there's rather a lot to like about the java ecosystem.
- za3faran 4y agoJust because it started off that way does not mean it remains so. As a matter of fact, it has come along very nicely in the past few years with many cool features including virtual threads, sealed types, pattern matching, records, and more.
- soulofmischief 4y agoYeah to be fair I have to give this same kind of spiel for JavaScript too.
- nogridbag 4y agoI must admit I use exceptions heavily for validation, e.g. checking input at API bounds. It makes the code fairly clean. I would imagine this is considered bad practice, but if there was no overhead this seems preferable over wrapping every call in some wrapper object. Any good links to further info on this?
- viraptor 4y agoIf you check for actual exceptional cases, you have next to no overhead. The overhead comes from creating the exception environment and going up the stack in unusual ways. For validation where most data is correct, this should have next to no impact.
- narag 4y agoFood for thought: what are the expected consequences of the exception? If the error will stop the program flow and show a warning dialog to the user, it's useless to think too much about performance. More or less the same if it's going to log some message and abort the operation. What is usually frowned upon is using the exception as a kind of goto for normal flow of the program. Exceptions should be... the exception. Otherwise all this performance brouhaha is a waste of time.
- em3rgent0rdr 4y ago"Exceptions should be... the exception." :)
- arcticbull 4y agoThe problem is exceptions have entirely opaque flow control. They're the opposite of a goto statement: a comes-from statement if you will. Flow control could originate literally anywhere down the stack and that makes reasoning about what's happening very difficult. Depending on the language it can also have a super broad and ever-changing surface area.
- groestl 4y ago
- xxs 4y agoOne note: did you run the code w/ warmup and all, or just OSR (on stack replacement).
- gpderetta 4y ago> converted the immediate throw-catch into a simple goto. This is very interesting. Both GCC and clang do not do that, as they represent exceptions as abnormal edges out of a basic block and don't optimize further. I guess in Java exceptions are common enough that it is worth the additional effort of transforming some of these cases into normal jumps when the destination is seen, while in C++ it is more of a vicious circle of exceptions not being optimized because they are uncommon and being used sparingly because they are optimized. It is of course possible that Java exceptions semantics are such that they might be easier to optimize (lots of observable side effects in C++ unfortunately).
- inkyoto 4y agoMoreover, different languages implement the exception handling differently. The article focuses on C++, which has a notion of object destructors (most – but not all – programming languages don't have the destructors). Implications for the exception handling are manyfold: upon an entry into a «try» block, a C++ compiler has to account for all objects created at the method (or function) scope up until this point and register their corresponding destructors in the exception unwinding table. Then, since C++ allows objects to be created on the stack (via RAII or an explicit object declaration), the call frame has to be correctly accounted for as well. Both of which are computationally expensive things to do. When an exception is thrown out, the «throw» statement results in a reverse walk back of the registered destructors first (apart from the objects created on the heap), and then adjusting the frame pointer and placing an exception object on the stack before returning from the method's (or function's) exception handler. All of that takes many CPU cycles and wreaks havoc on instruction scheduling, pipelines, the TLB and stuff, therefore making the exception handling very expensive in C++ with little room left for optimisations. Exception handling performance in earlier revisions of C++ was abysmal. It is also all C++ specific and does not apply to other programming languages. Java, for instance, doesn't do that, and leaves the heap clean-up (where all Java objects are created anyway) to the garbage collector, so the exception handling is less taxing in Java – at the exception raising point. P.S. The above is a gross oversimplification of how the exception handling works in C++, but it should it give a rough idea of why the author has observed a slowdown at an orders of magnitude scale.
- pjmlp 4y agoIt also depends on the implementation, for example VC++ can ping back on Win32 structured exception handling, which other OSes don't have.
- noobermin 4y agoWhy not just use a branch friend, why not? None of the uses you listed is faster but at best case is "equivalent" why avoid it at all? I know the article is about performance but from a sheer programmjng perspective brqnches are there for a reason.