7

It's often said that you shouldn't use exceptions for regular error handling because of bad performance. My guess is that that bad performance is caused by having to instantiate a new exception object, generate a stack trace, etc. So why not have lightweight exceptions? Code like this is logically sound:

string ageDescription = "Five years old";
try {
    int age = int.Parse(ageDescription);
}
catch (Exception) {
    // Couldn't parse age; handle parse failure
}

And yet we're recommended to use TryParse instead to avoid the overhead of the exception. But if the exception were just a static object that got initialized when the thread started, all the code throwing the exception would need to do is set an error code number and maybe an error string. No stack trace, no new object instantiation. It would be a "lightweight exception", and so the overhead for using exceptions would be greatly reduced. Why don't we have such lightweight exceptions?

Jez
  • 27,951
  • 32
  • 136
  • 233

6 Answers6

5

The exception object instantiation is the smallest problem in the whole case. The real performance killer is that the control flow must stop executing your program and has to look up the call stack for possible handlers (catch blocks) that can catch the thrown exception, then it has to execute the correct ones (and their finally blocks), rethrow exceptions when told so and then continue executing the program on the right place, i.e. after the last handler. Your idea of "lightweight" exceptions would change nothing of this, it would even slow down creation of threads, because it would have to create and store the exception object, and would prevent exception filtering by type, which is now possible.

By using TryParse, you avoid all this by a simple conditional clause, also you actually write less code and it is much easier to read and reason about.

Exceptions are for exceptional cases and in such scenarios, they provide lots of useful information for logs/debugger.

Honza Brestan
  • 10,637
  • 2
  • 32
  • 43
  • Not sure I agree with the "would prevent exception filtering by type" part. The static object could surely store "exception type" which would maybe be an enum value. – Jez Dec 08 '12 at 19:54
  • @Jez: Maybe you could contrive a less functional type of exception which you could use in parallel with C#'s exception handling. But to do so would mean having two exception types which would be quite inconsistent and confusing for programmers, each with differing capabilities. And what, then, would be the point of creating two ways of doing the same thing, splintering the language functionality in the name of optimization if you could just use a best practices and use things like Int32.TryParse() when performance really *is* important? – Dave Markle Dec 08 '12 at 19:59
  • When would you set what exception is being "thrown"? How would you add custom exceptions? Also it would often end up with a `switch` of some sort in the catch clause. Check out **[exception filters](http://msdn.microsoft.com/en-us/library/8a9f2ew0%28v=vs.80%29.aspx)** in VB, they are also an interesting example, what these exceptions can do – Honza Brestan Dec 08 '12 at 19:59
  • @HonzaBrestan 1) In the `throw` statement. 2) By setting the exception "type" to whatever value you want that will be meaningful to your `catch`ing code. 3) Why would it end up with a `switch`? – Jez Dec 08 '12 at 20:02
  • 1) Fair enough, but I can see no way of specifying the type without creating an instance of something. 2) So you want to have dynamic exception? Or a string? 3) What if my code can throw 4 different exceptions and I want to react differently to each one? – Honza Brestan Dec 08 '12 at 20:05
  • @DaveMarkle: Consider the following scenarios: (1) `SomeCollection.Add()` fails with `InvalidOperationException` because the item already exists (2) `SomeCollection.Add` calls some method which *unexpectedly* throws `InvalidOperationException`. I would suggest there should be some value in allowing the code which calls `SomeCollection.Add` to distinguish between the exception which passed through a caller that wasn't "expecting" it, versus the one that never did. If I had my druthers, the first situation would be a "checked" exception, but... – supercat Dec 06 '13 at 23:03
  • ...rather than forcing extra baggage on method callers that aren't prepared to handle checked exceptions the way Java does, I would specify that code which doesn't catch a checked exception thrown by a method must declare (concisely) whether such exceptions should be passed through, or propagate as unchecked exceptions. Checked-ness would not be a function of the exception type, but rather the throw site and catch site. Note that a method which throws a checked exception need not use exception-handling mechanisms; it could simply have the exception as an extra part of its return value. – supercat Dec 06 '13 at 23:08
  • Code is WAY simpler and more effective when a function either returns the expected result or throws an exception. If a method, for example, that's supposed to return an object, accepts three identifiers.... it's easier to parse all three, throwing an exception on any failure, than it is to accumulate three tryparses, examine all three results, and then complicate the return value of the function to include success or failure. It's a much simpler programming model to have functions return a valid result or throw, and have callers handle the throw. Enough about 'exceptional' circumstances. – Triynko Nov 15 '17 at 03:35
4

The performance hit isn't just because you're creating a new Exception object. A lot of it has to do with the conditional unwinding of the stack that needs to be done when an exception occurs.

For example, think about the work that would have to be done when you have exception handlers that catch different kinds of exceptions. At each point in the stack, as it's unwound from callee to caller, the language must do a type check to see not only if the exception can be handled, but what the most appropriate handler is. That's a significant amount of overhead in its own right.

If you really want to be lightweight, you should return a result from your functions -- that's what Int32.TryParse() does. No stack unwinding, no type checking, just a simple condition which can easily be optimized for.

EDIT: One somewhat interesting thing to note is that C# was created after Java. Java has a couple of interesting constructs which cause exception handling to be more complicated than what we see in C#, namely checked exceptions and the throws keyword. Kind of interesting reads. I'm (personally) glad that C# didn't include this "feature". My guess is that they bifurcated their exception handlers to boost performance. In the real world, as I understand it, a LOT of developers just end up specifying throws exception in their function declarations.

Dave Markle
  • 95,573
  • 20
  • 147
  • 170
  • 1
    That type check is using reflection, no? With a "lightweight exception", instead of checking the type of the exception instantiation, you could just use an enum to specify the "type" of exception and compare with that. – Jez Dec 08 '12 at 19:57
  • 4
    Like I said as an comment to Honza's excellent answer, the C# language designers could have done a number of things. The core answer to why something isn't in the language, and I suspect the answer to your question is, "though it could be possible, the inconsistency isn't worth it when there's another, simpler way of achieving the same result (high performance)". – Dave Markle Dec 08 '12 at 20:02
  • 1
    We have to catch exceptions from components, even those that return error codes, because at any time they might throw due to unforeseen errors. Doesn't this mean that the benefit from returning error codes is now lost? – Ryan Burbidge Oct 12 '14 at 05:53
  • 2
    @user3339997: Yes, it does mean that. The pattern of returning error codes should be generally be avoided in C# code. – Dave Markle Oct 14 '14 at 12:18
  • 1
    Just want to chime in a few years later and state that this answer is now outdated in its performance claims. Lightweight exceptions *can* be fast - [OCaml's lightweight exceptions are faster than even a `setjmp` in C.](https://stackoverflow.com/questions/8564025/ocaml-internals-exceptions) It just requires a specialized calling convention that supports lightweight exceptions (and they use caller-saved registers exclusively, which simplifies their end). – Claudia Jun 12 '19 at 07:18
3

The problem with exceptions isn't just generating the exception itself, that's honestly not even the most time consuming part. When you throw an exception (after it has been created) it needs to unwind the stack going through each scope level, determining if that scope is a try/catch block that would catch this exception, update the exception to indicate it went through that section of the stack, and then tearing down that section of the stack. And then of course there are all of the finally blocks that may need to be executed. Making the Exception itself store less information wouldn't really simplify any of that.

Servy
  • 202,030
  • 26
  • 332
  • 449
  • 1
    I don't think the stack unwinding is expensive in comparison to the stack trace collection. After all, collecting the stack trace requires visiting *every* stack frame and possibly do symbol lookups; not just visit and have a condition or two on each try in between the throw site and catch site. – Billy ONeal Dec 08 '12 at 19:45
2

You should use int.TryParse in your case. And it is faster and more readable to test some conditions then to throw and catch exception. Use exceptions for exceptional situations not for regular validation.

Kirill Bestemyanov
  • 11,946
  • 2
  • 24
  • 38
  • 1
    For god's sake, this answer doesn't even attempt to answer my question. -1. The fact that people are voting this up shows that they probably haven't read the question either. – Jez Dec 08 '12 at 19:49
  • 1
    For god's sake, if you want to shot in your foot, you can do it, but i don't recommend. But there are some useful mechanics for regular error handling other than throwing exceptions. Use it. – Kirill Bestemyanov Dec 08 '12 at 19:56
  • 2
    @Jez: I think the part "Use exceptions for exceptional situations" answers your question as to why it is the way it is. – myermian Dec 08 '12 at 19:57
  • @m-y What, write some oft-repeated platitude and watch a bunch of people click the upvote button without even considering whether it actually answers the question? Sigh. – Jez Dec 08 '12 at 19:58
2

Because the utility offered by the "heavyweight" exceptions is exceptionally (ha ha) useful. I can't tell you how often I've wanted the ability to dump something like a stack trace in C land without having to ask people to yank out a debugger.

Generating things like the stack trace after the fact (i.e. after the exception has been caught on demand) are infeasible because once the exception has been caught, the stack has been unwound -- the information is gone. You want information about the point of failure; so the data must be collected at the point of failure.

As for the "new object instantiation" -- that is so cheap in comparison to other expensive exception features (unwinding the stack, stack trace, multiple function exit points, etc.) that it isn't worth worrying about.

Billy ONeal
  • 104,103
  • 58
  • 317
  • 552
  • 1
    But I'm not talking about doing away with regular exceptions; I'm talking about adding a new form of lightweight exception to address the performance issue in cases where you'd rather write your code like my example, instead of checking return values all over the place. – Jez Dec 08 '12 at 19:52
  • 1
    @Jez: The only use case for your proposed "lightweight exceptions" is in the rare case that an exception is thrown and intended to be handled by a function's immediate caller. In such cases, the propsed "lightweight exceptions" would be functionally equivalent to error codes; and provide little in the way of benefit there. That defeats the point of exceptions. Exceptions are useful when the point where an error must be handled is far from where the error actually occurs. – Billy ONeal Dec 08 '12 at 20:44
1

You're not recommended to use TryParse instead of Parse because of performance. If there's any chance a parse can fail (because it's user generated input for example) then the failure to parse is not exceptional, it's to be expected. As the name implies, exceptions are for exceptional circumstances. For stuff that should have been caught earlier, but wasn't, so unexpected that you can't really continue.

If a function expects an object but instead null is passed in, then it's up to the designer of the method what the right thing to do is in this case. If the parameter is an optional override for a default, the program ca continue and use the default or ignore the parameter. But otherwise, the program should simply throw a ArgumentNullException.

Performance shouldn't be a consideration at all when deciding to use exceptions or not. It's a matter of intent and purpose. They're not even that slow, sure they're many times slower than adding two integers, but I can still throw 50,000 exceptions per second on my aging Core 2 Duo. If the use of exceptions ever becomes a bottleneck, you're not using them in the right way.

JulianR
  • 16,213
  • 5
  • 55
  • 85
  • "If the use of exceptions ever becomes a bottleneck, you're not using them in the right way." Nice! But does that apply in C++ too? – Paul Stelian Jan 22 '18 at 15:04