13

Is this

try
  DoSomethingThatMightThrowAnException;
except
  on E : ESyntaxError do
    begin
    if (E.ErrorCode = errMissingBracket) then
      HandleError
    else
      raise;
    end;
end;

slower than this?

try
  DoSomethingThatMightThrowAnException;
except
  on E : EMissingBracketSyntaxError do
    begin
    HandleError;
    end;
end;

What's the difference to be expected? Does it matter? Note that this could happen several times through the call stack.

jpfollenius
  • 16,456
  • 10
  • 90
  • 156
  • 7
    Unless you have thousands of exceptions per second.. I think it does not matter. – Kromster Jun 22 '11 at 10:49
  • 1
    I suspect the first variant is faster (because integer equality testing is fast) while the second would be somewhat slower (not saying slow, just slower) because it requires testing class inheritance (is the `EMissingBracketSyntaxError_V2` actually a `EMissingBracketSyntaxError`?). Unfortunately the code for unwinding exceptions is not found in `System.pas` and I can't find the place where the test actually takes place. In the end it doesn't even matter, since Exceptions are Exceptional. – Cosmin Prund Jun 22 '11 at 11:17
  • Ceteris paribus, first snippet **always** performs one ErrorCode comparison more, thus is slower. Also, matching class type loop (as in `InheritsFrom`) completes in fewer iterations with more specific class in the second snippet. – Premature Optimization Jun 22 '11 at 11:27
  • 3
    @Downvoter, a single `InheritsFrom` test is enough to offset several integer comparisons, because the `InheritsFrom` test is actually a recursive integer comparison test: all parents of the checked class are tested, until a parent of the required class is found OR there are no more parents. – Cosmin Prund Jun 22 '11 at 11:51
  • 1
    @Cosmin Prund, given `ESyntaxError → EMissingBracketSyntaxError` hierarchy and given actual `E` exception `is EMissingBracketSyntaxError`, wouldn't `InheritsFrom` complete in **zero** iterations for the **second snippet** and in **one** iteration for the **first snippet**? always at least one more iteration and then more cost added for ErrorCode comparison – Premature Optimization Jun 22 '11 at 13:07
  • 2
    @Downvoter, I assume there are other types of `ESyntaxError` beyond `EMissingBracketSyntaxError`; One *failed* `InheritsFrom` requires minimum 4 tests (`ESpecificSyntaxError` → `ESyntaxError` → `Exception` → `TObject`); 3 tests for the first sample: `ESpecificSyntaxError` → `ESyntaxError` then the test for `ErrorCode`. I assume it gets worst if multiple `on E:ESpecificException` handlers are used with the same `try-except` block. And counting comparisons like this is a gross simplification of what's going on `InheritsFrom`. – Cosmin Prund Jun 22 '11 at 13:35
  • @Cosmin Prund, ah, got your point about **failed** case and *cases* (except what InheritsFrom loop should never ascend beyond `Exception` in the non-corrupt exception handling code). – Premature Optimization Jun 22 '11 at 14:30
  • 1
    @Cosmin, exception handlers don't use `InheritsFrom`; check out `_HandleOnException` in *System.pas*. They use a special inline version that checks instance size and class name. – Rob Kennedy Jun 22 '11 at 22:08
  • 1
    @Downvoter, you seem to forget that you can throw any object type, not just descendants of `Exception`. – Rob Kennedy Jun 22 '11 at 22:08

4 Answers4

2

What's the difference to be expected?

The difference between the scenarios you described is minimal.
However there is a signifcant difference between raising an exception and not raising one at all (using error results).

Does it matter? Note that this could happen several times through the call stack.

You should only use exceptions for "exceptional situations". If the error is frequent, especially for example in a loop then it deserves to be elevated to a fully fledged use-case scenario.

If you don't do this, what starts out seemingly simple, quickly deteriorates into a situation where your except block becomes bigger than the rest of your routine.

Ordinarily it should be quite trivial to check the condition and deal with it as an explicit branch in main-line code.

I.e. instead of:

begin
  try
    //Do1
    //Do2 (possibly raising an exception that you can handle)
    //Do3
    //Do4
  except
    //Dealing with main-line cases in exception handlers is
    //very bad, leading to difficult to read code in the future.
  end;
end;

Rather write:

begin
  //Do1
  //LDo2Result := Do2
  //NOTE: Do2 can still raise exceptions in EXCEPTIONAL situations.
  //  But for "main-line" use-case scenarios should rather return explicit 
  //  results that can be handled.
  if LDo2Result = d2rNoErrors then
  begin
    //Do3
    //Do4
  end;
  if LDo2Result = d2rBracketMissing then
  begin
    //DoX
  end;
end;

The above is generally better in both performance and maintainability than either of the scenarios you describe. However, as with all things software development related: you get a pallette of guidlines and techniques, but you need to apply your experience to choose "the best tool for the specific job currently at hand".

Stijn Sanders
  • 35,982
  • 11
  • 45
  • 67
Disillusioned
  • 14,635
  • 3
  • 43
  • 77
  • +1 for the result handling example. I would use the same; return the result out of each particular function and decide what to do rather than re-raise the exception again. But it depends a lot on specific situation. Anyway I like how to do this most of the WinAPI functions; they return the result and if you want to check what actually happened at failure you can get it using common `GetLastError`. –  Jun 22 '11 at 17:10
1

Unless your program's logic severely relies on exceptions (which is probably a sign of bad design) I think it will hardly matter since exception handling will only be 0.5% of the cpu time your application takes.

But taking a wild guess, I don't think there will be much of a performance difference, since internally the exception will be passed on either way.

On the other hand, I prefer the second method a lot more since you are expressing what you want syntacticly in the language, which is good. But I understand that method one can be preferred in some cases, especially when the context is bigger and more complex.

Disclaimer: I have never programmed in Delphi and I know nothing about the internals of the language. There might as well be a huge difference in performance, I don't know.

orlp
  • 112,504
  • 36
  • 218
  • 315
1

The performance difference is negligible in the context of a real-world application. On my machine raising and handling an exception (with a null handler) takes about 0.3 milliseconds, and if I add extensive logging, abut 1.3 milliseconds. So if the exceptions are truly exceptional, it will not make one jot of difference to your application's performance.

Misha
  • 1,816
  • 1
  • 13
  • 16
  • -1, the second block is only invoked for Exceptions of the `EMissingBracketSyntaxError` class, so it's not true that the second handler is handling "all exceptions, regardless of severity". The first statement ("it won't matter performance-wise") is also wrong: the difference might not be significant, but it does exist. – Cosmin Prund Jun 22 '11 at 11:24
  • @Cosmin, text adjusted after re-reading the code. However, in real-world terms (as pointed out above), the difference is insignificant in terms of application performance. – Misha Jun 22 '11 at 11:42
  • 1
    I agree. I mostly asked out of curiosity. – jpfollenius Jun 22 '11 at 11:58
  • downvote revoked, but your calculations are most likely off. With a "null" handler I'm getting about `0.01` milliseconds per raised-and-handled exception. I doubt my system is 30 times faster then yours! – Cosmin Prund Jun 22 '11 at 12:00
  • @Cosmin, it seems I pulled in the JCL default exception handling. Anyway it illustrates the point about the perfomance impact being minimal (even for heavy default handling). – Misha Jun 22 '11 at 12:14
0

I've had a quick look at the assembler the compiler spews out for above code snippets. Turns out that the bytes right after jmp @HandleOnExeption contain data such as the exception class pointers you use in the on clauses (if any).

I'm not that well versed in assembler to know exactly what's going on, but enough to understand what is roughly going on and come to this conclusion:

I suspect System.pas' HandleOnException does a call @IsClass already, and passes the exception on if no suitable handler is found, so if you use on e:Exception and re-raise, this will add a little code and make two calls extra:

  • one back to your exception handling section (in all cases)
  • one call @RaiseAgain (in cases the exception gets re-raised)

So, there's a difference. A minor one, but still, it's there.

Stijn Sanders
  • 35,982
  • 11
  • 45
  • 67