How slow are Java exceptions?

2018-12-31 04:25发布

Question: Is exception handling in Java actually slow?

Conventional 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. it is really slow - even an order of magnitude slower than regular code (the reasons given vary),

and

  1. it is 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 executes 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 the code sample, test comparisons, or results in the last year or two that show exception handling in Java to actually be slow.

What leads 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 their 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 presence 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.

17条回答
倾城一夜雪
2楼-- · 2018-12-31 04:45

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.

查看更多
流年柔荑漫光年
3楼-- · 2018-12-31 04:48

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.

查看更多
柔情千种
4楼-- · 2018-12-31 04:48

I changed @Mecki 's answer above to have method1 return a boolean and a check in the calling method, as you cannot just replace an Exception with nothing. After two runs, method1 was still either the fastest or as fast as method2.

Here is snapshot of the code:

// Calculates without exception
public boolean method1(int i) {
    value = ((value + i) / i) << 1;
    // Will never be true
    return ((i & 0xFFFFFFF) == 1000000000);

}
....
   for (i = 1; i < 100000000; i++) {
            if (t.method1(i)) {
                System.out.println("Will never be true!");
            }
    }

and results:

Run 1

method1 took 841 ms, result was 2
method2 took 841 ms, result was 2
method3 took 85058 ms, result was 2

Run 2

method1 took 821 ms, result was 2
method2 took 838 ms, result was 2
method3 took 85929 ms, result was 2
查看更多
临风纵饮
5楼-- · 2018-12-31 04:50

My opinion about Exception speed versus checking data programmatically.

Many classes had String to value converter (scanner / parser), respected and well-known libraries too ;)

usually has form

class Example {
public static Example Parse(String input) throws AnyRuntimeParsigException
...
}

exception name is only example, usually is unchecked (runtime), so throws declaration is only my picture

sometimes exist second form:

public static Example Parse(String input, Example defaultValue)

never throwing

When the second ins't available (or programmer read too less docs and use only first), write such code with regular expression. Regular expression are cool, politically correct etc:

Xxxxx.regex(".....pattern", src);
if(ImTotallySure)
{
  Example v = Example.Parse(src);
}

with this code programmers hasn't cost of exceptions. BUT HAS comparable very HIGH cost of regular expressions ALWAYS versus small cost of exception sometimes.

I use almost always in such context

try { parse } catch(ParsingException ) // concrete exception from javadoc
{
}

without analysing stacktrace etc, I believe after lectures of Yours quite speed.

Do not be afraid Exceptions

查看更多
孤独寂梦人
6楼-- · 2018-12-31 04:53

My answer, unfortunately, is just too long to post here. So let me summarize here and refer you to http://www.fuwjax.com/how-slow-are-java-exceptions/ for the gritty details.

The real question here is not "How slow are 'failures reported as exceptions' compared to 'code that never fails'?" as the accepted response might have you believe. Instead, the question should be "How slow are 'failures reported as exceptions' compared to failures reported other ways?" Generally, the two other ways of reporting failures are either with sentinel values or with result wrappers.

Sentinel values are an attempt to return one class in the case of success and another in the case of failure. You can think of it almost as returning an exception instead of throwing one. This requires a shared parent class with the success object and then doing an "instanceof" check and a couple casts to get the success or failure information.

It turns out that at the risk of type safety, Sentinel values are faster than exceptions, but only by a factor of roughly 2x. Now, that may seem like a lot, but that 2x only covers the cost of the implementation difference. In practice, the factor is much lower since our methods that might fail are much more interesting than a few arithmetic operators as in the sample code elsewhere in this page.

Result Wrappers, on the other hand, do not sacrifice type safety at all. They wrap the success and failure information in a single class. So instead of "instanceof" they provide an "isSuccess()" and getters for both the success and failure objects. However, result objects are roughly 2x slower than using exceptions. It turns out that creating a new wrapper object every time is much more expensive than throwing an exception sometimes.

On top of that, exceptions are the language supplied the way of indicating that a method might fail. There's no other way to tell from just the API which methods are expected to always (mostly) work and which are expected to report failure.

Exceptions are safer than sentinels, faster than result objects, and less surprising than either. I'm not suggesting that try/catch replace if/else, but exceptions are the right way to report failure, even in the business logic.

That said, I would like to point out that the two most frequent ways of substantially impacting performance I've run across are creating unnecessary objects and nested loops. If you have a choice between creating an exception or not creating an exception, don't create the exception. If you have a choice between creating an exception sometimes or creating another object all the time, then create the exception.

查看更多
冷夜・残月
7楼-- · 2018-12-31 04:53

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.

查看更多
登录 后发表回答