Exceptions and performance

I know people say that one should only use exceptions in exceptional circumstances, but there are times when it seems to me that using try & catch would make my code a lot simpler than the alternative - a number of condtional return statements. Can the cost of throwing exceptions be avoided by instead of throwing a new exception, throwing a constant that extends the class Throwable?

E.g.

public class TestThrowable {
      
      static final Throwable CONSTANT_THROWABLE= new Throwable(){};

      public static void main(String[] args) {
            try{
                  throw CONSTANT_THROWABLE;
                  
            }catch(Throwable t){
                  System.out.println("Caught the throwable!");                  
            }
            
      }
}

Luke

I think the expensive operation is catching the exception, not how you throw it.

I think the expense is also incurred at ‘throw’ time as the exception has to be filled with a stack trace and numerous other small tweaky things need to be done in the VM.

Cas :slight_smile:

A seminar at the last JavaOne covered this situation. There is still a small overhead in the try/catch handling itself, but Cas is right, most of the expense is in filling in the stack trace. A constant throwable should give you better performance.

It seems that the stack trace is created when the Throwable is created, not when it is thrown, e.g.


public class TestThrowable {

      static final Throwable CONSTANT_THROWABLE = new Throwable() {
      };

      public static void main(String[] args) {
            try {
                  throw CONSTANT_THROWABLE;

            } catch (Throwable t) {
                  System.out.println("Caught the throwable!");
                  t.printStackTrace();
            }
      }
}

Outputs:


Caught the throwable!
TestThrowable$1
      at TestThrowable.<clinit>(TestThrowable.java:3)

Does this mean that if I use constant Throwables, the performance of



            try {
                  doSomething();
            } catch (Throwable t) {
                  cleanUp();
            }


will be approximately the same as




            if (canSomething()) {
                  doSomething();
            } else {
                  cleanUp();
            }


I.e. can I expect the overhead of the try/catch handling to be equivalent to the overhead of calling an extra method, canDoSomething(), and checking the result.

That’s pretty weird isn’t it! I suppose I’ve never noticed because I only throw them where I want them…

But no, the overhead of throw/catch is massively greater than calling another method. I think this will even show up in a fairly small benchmark.

The Hotspot guys have designed their compiler to cater for the way it should be done; there’s a lot more thinking gone in to making non-exceptional code function very fast instead of exceptional code which should theoretically be called in an exceptionally unusual circumstance.

Cas :slight_smile:

I did a quick test to see what would happen, the results were as follows:


When there are 10000000 iterations, and an exception every 2 iterations.
Using try/catch and constant Throwable: 8322ms
Using if(canDoSomething()): 1663ms
Using try/catch and new Throwable: 116547ms

When there are 10000000 iterations, and an exception every 5 iterations.
Using try/catch and constant Throwable: 4076ms
Using if(canDoSomething()): 1993ms

When there are 10000000 iterations, and an exception every 12 iterations.
Using try/catch and constant Throwable: 2093ms
Using if(canDoSomething()): 2233ms

When there are 10000000 iterations, and an exception every 100 iterations.
Using try/catch and constant Throwable: 1252ms
Using if(canDoSomething()): 2223ms


my canDoSomething() method was:



      static boolean canDoSomething() {
            if ((number % modulus) == 1) {
                  return false;
            } else {
                  return true;
            }
      }

This suggests that:
(1) the cost of throwing exceptions can be reduced by a factor of at least 10 by using constants;
(2) if exceptions are occur less frequently than 1 time in 12, then catching constant Throwables when exceptions occur is faster than calling canDoSomething() and avoiding throwing exceptions. (Obviously, this depends on how long the canDoSomething() takes to execute).

But its only a microbenchmark.

Luke

Is this simply a result of where the test condition needs to go?

I mean,obiviously, there is a

if( something() ) throw MyException;

and the key is to avoid the test that the something happened where it will be tested every loop iteration.

Could you possibly use loop lables to get the same sort of functionality?

eg.


do
{
  special_case:
  do
  {
      // various logic possibly deeper nesting of loops
      if( something() )  break special_case;

  } while(true);
   
  // handle special case
  // could continue main loop
  // or break out of this one
} while( true);

Run that lot under -server and see what happens - might be interesting…

Cas :slight_smile:

I’ve been using exceptions for quite a few things in the application server that I’ve been building for work. I haven’t yet seen exceptions becoming a problem because they’re only ever triggered during true error conditions when in most cases I need a stack trace going to log4j and a page going to a developer. Since I never have exceptions involved in application logic at all, I’ve never noticed any real issues and my application needs to churn through 14k business logic operations per second. Right now my app isn’t even breaking a sweat - let alone causing any issues for the Solaris box hosting it. There may be places for improvement in the exception handling code, but is one of those situations where the benefit is so trivially small compared to having to do something that’s outside the standard Java coding paradigm.

So I keep listing this type of thing in the ‘optimization for the sake of doing optimization’ category. Heck even on my PowerBook G4 I see very little point in going against the default behavior of Java. An exception should be a rare occurrence - else it isn’t an exception… its a normal part of business logic that belongs in its own method :slight_smile:


D:\Dev\test>C:\j2sdk1.4.0\bin\java -server TestThrowable2

When there are 10000000 iterations, and an exception every 2 iterations.
Using try/catch and constant Throwable: 1302ms
Using if(canDoSomething()): 1933ms
Using try/catch and new Throwable: 90129ms

When there are 10000000 iterations, and an exception every 5 iterations.
Using try/catch and constant Throwable: 1102ms
Using if(canDoSomething()): 2123ms
Using try/catch and new Throwable: 36482ms

When there are 10000000 iterations, and an exception every 12 iterations.
Using try/catch and constant Throwable: 1162ms
Using if(canDoSomething()): 2293ms
Using try/catch and new Throwable: 15923ms

When there are 10000000 iterations, and an exception every 100 iterations.
Using try/catch and constant Throwable: 1112ms
Using if(canDoSomething()): 2213ms
Using try/catch and new Throwable: 2894ms

When there are 10000000 iterations, and an exception every 1000 iterations.
Using try/catch and constant Throwable: 1102ms
Using if(canDoSomething()): 2203ms
Using try/catch and new Throwable: 1292ms

Not really what I would have expected, but maybe its just a quirk of the microbenchmark.

I was thinking about using exceptions in non ‘exceptional circumstances’ because it made the code clearer, however, I think swpalmer’s suggestion of using breaks with labels solves the problem without using exceptions in a way they were not designed for.

The key issue with using the language in a way that it was not intended means you are effectively abusing an instance of its implementation, namely Sun’s 1.4 JRE in this case. What about IBM’s VM? What about the Mac port? And Linux? They are all very unlikely to treat exceptions in the same way. Furthermore, Sun can and probably will alter their VM to make the case without exceptions faster at some point in the future. Your existing code will get slower! Agh!

Cas :slight_smile:

Another cost of exceptions is in optimizations: thr try/catch blocks will make harder for the optimizer to predict the program’s control flow, especially when the exception is thrown by one method and caught by another. This will spoil inlining, constant propagation, register allocation etc., especially if the throw lives inside loops.

The JGF benchmarks revel that Sun HotSpot Server 1.4.x is pretty good with this – so good that it detects simple loops of try/catchs (local throw, with pre-created exception) as dead code and produces infinite scores – but the same doesn’t happen with non-local throws, non-precreated exceptions, or in any case with any JDK 1.3.1 (including IBM’s) or even with HotSpot Client (more likely to be used by games).