views:

2547

answers:

12

Question: Is excepion handling in Java actually slow?

Conventinal wisdom, as well as a lot of Google results, says that exceptional logic shouldn't be used for normal program flow in Java. Two reasons are usually given, 1) its really slow - even an order of magnitude slower than regular code (the reasons given vary), and 2) its messy because people expect only errors to be handled in exceptional code. This question is about #1.

As an example, this page describes Java exception handling as "very slow" and relates the slowness to the creation of the exception message string - "this string is then used in creating the exception object that is thrown. This is not fast." The article Effective Exception Handling in Java says that "the reason for this is due to the object creation aspect of exception handling, which thereby makes throwing exceptions inherently slow". Another reason out there is that the stack trace generation is what slows it down.

My testing (using Java 1.6.0_07, Java HotSpot 10.0, on 32 bit Linux), indicates that exception handling is no slower than regular code. I tried running a method in a loop that exectues some code. At the end of the method, I use a boolean to indicate whether to return or throw. This way the actual processing is the same. I tried running the methods in different orders and averaging my test times, thinking it may have been the JVM warming up. In all my tests, the throw was at least as fast as the return, if not faster (up to 3.1% faster). I am completely open to the possibility that my tests were wrong, but I haven't seen anything out there in the way of code sample, test comparisons, or results in the last year or two that show exception handling in Java to actually be slow.

What lead me down this path was an API I needed to use that threw exceptions as part of normal control logic. I wanted to correct them in ther usage, but now I may not be able to. Will I instead have to praise them on their forward thinking?

In the paper Efficient Java exception handling in just-in-time compilation, the authors suggest that the presense of exception handlers alone, even if no exceptions are thrown, is enough to prevent the JIT compiler from optimizing the code properly, thus slowing it down. I haven't tested this theory yet.

+6  A: 

Even if throwing an exception isn't slow, it's still a bad idea to throw exceptions for normal program flow. Used this way it is analogous to a GOTO...

I guess that doesn't really answer the question though. I'd imagine that the 'conventional' wisdom of throwing exceptions being slow was true in earlier java versions (< 1.4). Creating an exception requires that the VM create the entire stack trace. A lot has changed since then in the VM to speed things up and this is likely one area that has been improved.

It would be good to define "normal program flow". Much has been written about using checked exceptions as a business process failure and an unchecked exception for non-recoverable failures, so in a sense, a failure in business logic could still be thought of as normal flow.
Spencer K
@Spencer K: An exception, as the name implies, means that an exceptional situation was discovered (a file went away, a network suddenly closed, ...). This implies that the situation was UNEXPECTED. If it is EXPECTED that the situation will occur, I'd not use an exception for it.
Mecki
@Mecki: right. I recently had a discussion with someone about this... They were writing a Validation framework and were throwing an exception in case of validation failure. I think this is a bad idea as this would be quite common. I'd rather see the method return a ValidationResult.
A: 

Why should exceptions be any slower than normal returns?

As long as you dont print the stacktrace to the terminal, saves it into a file, or something similar, the catch-block doesnt do any more work than other code-blocks. So I can't imagine why "throw new my_cool_error()" should be that slow..

Good question, and i'm looking forward to further information on this topic!

qualbeen
The exception has to capture the information about the stack trace, even if it doesn't actually get used.
Jon Skeet
+1  A: 

I think the first article refer to the act of traversing the call stack and creating a stack trace as being the expensive part, and while the second article doesn't say it, I think that is the most expensive part of object creation. John Rose has an article where he describes different techniques for speeding up exceptions. (Preallocating and reusing an exception, exceptions without stack traces, etc)

But still - I think this should be considered only a necessary evil, a last resort. Johns reason for doing this is to emulate features in other languages which aren't (yet) available in the JVM. You should NOT get into the habit of using exceptions for control flow. Especially not for performance reasons! As you yourself mention in #2, you risk masking serious bugs in your code this way, and it will be harder to maintain for new programmers.

Microbenchmarks in Java are surprisingly hard to get right (I've been told), especially when you get into JIT territory, so I really doubt that using exceptions is faster than "return" in real life. For instance, I suspect you have somewhere between 2 and 5 stack frames in your test? Now imagine your code will be invoked by a JSF component deployed by JBoss. Now you might have a stack trace which is several pages long.

Perhaps you could post your test code?

Lars Westergren
+1  A: 

HotSpot is quite capable of removing exception code for system generated exceptions, so long as it is all inlined. However, explicitly created exception and those otherwise not removed spend a lot of time creating the stack trace. Override fillInStackTrace to see how this can affect performance.

Tom Hawtin - tackline
+2  A: 

Why don't you write your own little test program to test the performance difference then?

Nailer
+1 for the rare common sense.
Bill K
+5  A: 

I've done some performance testing with JVM 1.5 and using exceptions was at least 2x slower. On average: Execution time on a trivially small method more than tripled (3x) with exceptions. A trivially small loop that had to catch the exception saw a 2x increase in self-time.

I've seen similar numbers in production code as well as micro benchmarks.

Exceptions should definately NOT be used for anything that's called frequently. Throwing a thousands of exceptions a second would cause a huge bottle neck.

For example, using "Integer.ParseInt(...)" to find all bad values in a very large text file--very bad idea. (I have seen this utility method kill performance on production code)

Using an exception to report a bad value on a user GUI form, probably not so bad from a performance standpoint.

Whether or not its a good design practice, I'd go with the rule: if the error is normal/expected, then use a return value. If it's abnormal, use an exception. For example: reading user inputs, bad values are normal--use an error code. Passing a value to an internal utility function, bad values should be filtered by calling code--use an exception.

James Schek
+26  A: 

It depends how exceptions are implemented. The simplest way is using setjmp and longjmp. That means all registers of the CPU are written to the stack (which already takes some time) and possibly some other data needs to be created... all this already happens in the try statement. The throw statement needs to unwind the stack and restore the values of all registers (and possible other values in the VM). So try and throw are equally slow, and that is pretty slow, however if no exception is thrown, exiting the try block takes no time whatsoever in most cases (as everything is put on the stack which cleans up automatically if the method exists).

Sun and others recognized, that this is possibly suboptimal and of course VMs get faster and faster over the time. There is another way to implement exceptions, which makes try itself lightning fast (actually nothing happens for try at all in general - everything that needs to happen is already done when the class is loaded by the VM) and it makes throw not quite as slow. I don't know which JVM uses this new, better technique...

...but are you writing in Java so your code later on only runs on one JVM on one specific system? Since if it may ever run on any other platform or any other JVM version (possibly of any other vendor), who says they also use the fast implementation? The fast one is more complicated than the slow one and not easily possible on all systems. You want to stay portable? Then don't rely on exceptions being fast.

It also makes a big difference what you do within a try block. If you open a try block and never call any method from within this try block, the try block will be ultra fast, as the JIT can then actually treat a throw like a simple goto. It neither needs to save stack-state nor does it need to unwind the stack if an exception is thrown (it only needs to jump to the catch handlers). However, this is not what you usually do. Usually you open a try block and then call a method that might throw an exception, right? And even if you just use the try block within your method, what kind of method will this be, that does not call any other method? Will it just calculate a number? Then what for do you need exceptions? There are much more elegant ways to regulate program flow. For pretty much anything else but simple math, you will have to call an external method and this already destroys the advantage of a local try block.

See the following test code:

public class Test {
    int value;


    public int getValue() {
     return value;
    }

    public void reset() {
     value = 0;
    }

    // Calculates without exception
    public void method1(int i) {
     value = ((value + i) / i) << 1;
     // Will never be true
     if ((i & 0xFFFFFFF) == 1000000000) {
      System.out.println("You'll never see this!");
     }
    }

    // Could in theory throw one, but never will
    public void method2(int i) throws Exception {
     value = ((value + i) / i) << 1;
     // Will never be true
     if ((i & 0xFFFFFFF) == 1000000000) {
      throw new Exception();
     }
    }

    // This one will regularly throw one
    public void method3(int i) throws Exception {
     value = ((value + i) / i) << 1;
     // i & 1 is equally fast to calculate as i & 0xFFFFFFF; it is both
     // an AND operation between two integers. The size of the number plays
     // no role. AND on 32 BIT always ANDs all 32 bits
     if ((i & 0x1) == 1) {
      throw new Exception();
     }
    }

    public static void main(String[] args) {
     int i;
     long l;
     Test t = new Test();

     l = System.currentTimeMillis();
     t.reset();
     for (i = 1; i < 100000000; i++) {
      t.method1(i);
     }
     l = System.currentTimeMillis() - l;
     System.out.println(
      "method1 took " + l + " ms, result was " + t.getValue()
     );

     l = System.currentTimeMillis();
     t.reset();
     for (i = 1; i < 100000000; i++) {
      try {
       t.method2(i);
      } catch (Exception e) {
       System.out.println("You'll never see this!");
      }
     }
     l = System.currentTimeMillis() - l;
     System.out.println(
      "method2 took " + l + " ms, result was " + t.getValue()
     );

     l = System.currentTimeMillis();
     t.reset();
     for (i = 1; i < 100000000; i++) {
      try {
       t.method3(i);
      } catch (Exception e) {
       // Do nothing here, as we will get here
      }
     }
     l = System.currentTimeMillis() - l;
     System.out.println(
      "method3 took " + l + " ms, result was " + t.getValue()
     );
    }
}

Result:

method1 took 972 ms, result was 2
method2 took 1003 ms, result was 2
method3 took 66716 ms, result was 2

As you can see, already the try block made the whole thing run slower. The catch block killed everything and made it 66 times slower!

As I said, the result will not be that bad if you put try/catch and throw all within the same method (method3), but this is a special JIT optimization I would not rely upon. And even when using this optimization, the throw is still pretty slow. So I don't know what you are trying to do here, but there is definitely a better way of doing it than using try/catch/throw.

Mecki
Thanks, I guess I hadn't gone that deep into the stack for my exception tests. This is exactly what I was looking for.
John Ellinwood
Ok, you're hired.
Chris Shouts
+2  A: 

I worked on some of the first java midlets for the first java-capable mobile phones.

We quickly learnt to write 'c-style' java.

However, one thing that sticks in the mind was iterating over every element in a Vector - it was substantially quicker to not check the bounds and catch an array-index-out-of-bounds exception than it was to check the bounds in the loop code!

Just a data point for you.

Will
+1  A: 

A while back I wrote a class to test the relative performance of converting strings to ints using two approaches: (1) call Integer.parseInt() and catch the exception, or (2) match the string with a regex and call parseInt() only if the match succeeds. I used the regex in the most efficient way I could (i.e., creating the Pattern and Matcher objects before intering the loop), and I didn't print or save the stacktraces from the exceptions.

For a list of ten thousand strings, if they were all valid numbers the parseInt() approach was four times as fast as the regex approach. But if only 80% of the strings were valid, the regex was twice as fast as parseInt(). And if 20% were valid, meaning the exception was thrown and caught 80% of the time, the regex was about twenty times as fast as parseInt().

I was surprised by the result, considering that the regex approach processes valid strings twice: once for the match and again for parseInt(). But throwing and catching exceptions more than made up for that. This kind of situation isn't likely to occur very often in the real world, but if it does, you definitely should not use the exception-catching technique. But if you're only validating user input or something like that, by all means use the parseInt() approach.

Alan Moore
which JVM did you use? is it still that slow with sun-jdk 6?
bene
I dug it up and ran it again under JDK 1.6u10 before submitting that answer, and those are the results I posted.
Alan Moore
A: 

There are some people here suggesting that throwing exceptions should ONLY be for the exceptional situation that you don't expect to happen. I believe this is foolish. The only reason why I might be inclined to agree is that exceptions are slow. But this is a Java PROBLEM that should be fixed. Checked exceptions are ways to ensure that your code's client handles all noteable exceptions, something akin to ML's pattern matching. It is elegant but slow, and I hope this is changed in future versions.

Exceptions provide a way to require that people use your code responsibly. Consider the example where we have a getCustomer(int customerId) method. You could have a version that returns null if there is no customer by that id, or a version that throws an InvalidCustomerIdException in the event that it cannot find it.

I personally like the latter, because the caller has to explicitly handle this situation and the method never returns null. This will help avoid an NPE.

Thomas Kinkade
A: 

Exceptions as the name suggests should be exceptional. Even if performance was not an issue I would suggest you used these rarely as exceptions tend to confuse people.

Peter Lawrey
A: 

Don't know if these topics relate, but I once wanted to implement one trick relying on current thread's stack trace: I wanted to discover the name of the method, which triggered the instantiation inside the instantiated class (yeap, the idea is crazy, I totally gave it up). So I discovered that calling Thread.currentThread().getStackTrace() is extremely slow (due to native dumpThreads method which it uses internally).

So Java Throwable, correspondingly, has a native method fillInStackTrace. I think that the killer-catch block described earlier somehow triggers the execution of this method.

But let me tell you another story...

In Scala some functional features are compiled in JVM using ControlThrowable, which extends Throwable and overrides its fillInStackTrace in a following way:

override def fillInStackTrace(): Throwable = this

So I adapted the test above (cycles amount are decreased by ten, my machine is a bit slower :):

class ControlException extends ControlThrowable

class T {
  var value = 0

  def reset = {
    value = 0
  }

  def method1(i: Int) = {
    value = ((value + i) / i) << 1
    if ((i & 0xfffffff) == 1000000000) {
      println("You'll never see this!")
    }
  }

  def method2(i: Int) = {
    value = ((value + i) / i) << 1
    if ((i & 0xfffffff) == 1000000000) {
      throw new Exception()
    }
  }

  def method3(i: Int) = {
    value = ((value + i) / i) << 1
    if ((i & 0x1) == 1) {
      throw new Exception()
    }
  }

  def method4(i: Int) = {
    value = ((value + i) / i) << 1
    if ((i & 0x1) == 1) {
      throw new ControlException()
    }
  }
}

class Main {
  var l = System.currentTimeMillis
  val t = new T
  for (i <- 1 to 10000000)
    t.method1(i)
  l = System.currentTimeMillis - l
  println("method1 took " + l + " ms, result was " + t.value)

  t.reset
  l = System.currentTimeMillis
  for (i <- 1 to 10000000) try {
    t.method2(i)
  } catch {
    case _ => println("You'll never see this")
  }
  l = System.currentTimeMillis - l
  println("method2 took " + l + " ms, result was " + t.value)

  t.reset
  l = System.currentTimeMillis
  for (i <- 1 to 10000000) try {
    t.method4(i)
  } catch {
    case _ => // do nothing
  }
  l = System.currentTimeMillis - l
  println("method4 took " + l + " ms, result was " + t.value)

  t.reset
  l = System.currentTimeMillis
  for (i <- 1 to 10000000) try {
    t.method3(i)
  } catch {
    case _ => // do nothing
  }
  l = System.currentTimeMillis - l
  println("method3 took " + l + " ms, result was " + t.value)

}

So, the results are:

method1 took 146 ms, result was 2
method2 took 159 ms, result was 2
method4 took 1551 ms, result was 2
method3 took 42492 ms, result was 2

You see, the only difference between method3 and method4 is that they throw different kinds of exceptions. Yeap, method4 is still slower than method1 and method2, but the difference is far more acceptable.

incarnate