Throwing Exceptions – slow and ugly

August 22, 2013 by Nikita Salnikov-Tarnovski

This post is about a historical experience in conjunction with recently applied performance optimization techniques. Years ago I was swearing at a particular application where I had to discover the undocumented behaviour buried under a truly clever engineering “technique”.

It was a typical monolithic Java EE application responsible for invoicing. The exact code is best to keep forgotten, but I recall that the developer had found a truly clever way to control the flow of a business process.

Part of the flow was a usual endless if-then-else mess, but what made things more “exciting” was that some random elements of those checks were buried into custom java.lang.RuntimeException handling mechanics. So you might have code similar to the following:

if (invoice.overdue()) {
  if (invoice.getCustomer().isKeyCustomer())
    throw new InvoiceOverdueException(InvoiceOverdueException.KEY_CUSTOMER);
  else
    doSomething();
}
else {
  if (invoice.getDueAmount() > BIG_AMOUNT)
    if (invoice.getCustomer().isKeyCustomer())
      //be silent
    else
      throw new InvoiceExceededException(invoice.getDueAmount());
}

And not short and easy to read blocks like the above but thousands of lines of code scattered along the whole application.

I guess you might agree with me that this is one hell of a good way to make yourself indispensable. There is no freakin way someone is going to understand why the application is behaving like it is.

I recalled this experience due to the recent Plumbr optimization task at hand. I would like to say our code was not using exceptions the way the previous case was describing but this would unfortunately not be completely true. One particular method still had a RuntimeException constructed and thrown during a regular flow of the code. I only discovered this lone villain due to the performance anomaly in this particular module.

Usually an exception is thrown only when facing unexpected problems. So we don’t expect exceptions to be thrown by thousands per second per thread. But as I, you might discover a method which uses exceptions for more likely events.

I only found the culprit due to the fact that this was a frequently used code block in a particular graph traversing algorithm so it was crucial to squeeze the last milliseconds out of it. Removing the exception handling immediately made this code block to complete more than 100 times faster.

This might make you wonder – why is exception handling slow? The slow part is related to constructing the exception. Or – to be more precise – any subclass of a java.lang.Throwable.

If you recall, all constructors invoke a call to superclass default constructor via calling super(). If you do not specify this call by yourself, the compiler is kind enough to add it into the bytecode itself. Anyhow, when looking into the java.lang.Throwable source code, you see the answer staring into your face:

    public Throwable() {
        fillInStackTrace();
    }

    public synchronized Throwable fillInStackTrace() {
        if (stackTrace != null ||
            backtrace != null /* Out of protocol state */ ) {
            fillInStackTrace(0);
            stackTrace = UNASSIGNED_STACK;
        }
        return this;
    }

    private native Throwable fillInStackTrace(int dummy);

So each time you create a new Throwable(), you end up filling the whole stack trace through the native call. If you do not believe this is slow, execute the following jmh microbenchmark to verify that creating exceptions is hundreds of times more expensive than constructing regular objects:

@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class NewExceptionTest {

  @GenerateMicroBenchmark
  public Object baseline() {
    return new Object();
  }

  @GenerateMicroBenchmark
  public Object exceptional() {
    return new RuntimeException();
  }
}
Benchmark                          Mode Thr    Cnt  Sec         Mean   Mean error    Units
j.NewExceptionTest.baseline        avgt   1      5    5        3.275        0.029  nsec/op
j.NewExceptionTest.exceptional     avgt   1      5    5     1329.266        8.675  nsec/op

To summarize this – the exceptions should be used for what the name indicates – for exceptional situations. If you start abusing the concept, you either make your code unreadable or start suffering from performance issues. At bare minimum I guarantee you get tons of negative karma from doing so.

For those of you who are more into good karma and Elysium – type of things, start following us in Twitter to stay up to date with the performance optimization tips and techniques.

Can't figure out what causes your OutOfMemoryError? Read more

ADD COMMENT

COMMENTS

How do you define ‘exceptional situation’?

You say that abuse can lead to performance issues. Then, do you define ‘exceptional situations’ as events that occur at low enough frequency to avoid performance penalty? If I have a hundred thousand files to open, and only one of them exists, would that mean that the file stream library authors abuse use of FileNotFoundException?

You say that abuse can lead to unreadable code. Then, do you define ‘exceptional situations’ as events that occur conveniently and simply enough such that the programmer can keep the code simple? That depends on the skill of the programmer. If the above code was refactored to be more simple, would it be ok to retain the exceptions?

In my mind, neither of those creiteria (keeping code simple, avoiding performance issues) help me identify an ‘exceptional situation’.

I think the contract with the caller would have greater weight than either of these two above. The contract of the above method could have been that the invoice is not in peril of being overdue. These exceptions are thrown to indicate to the caller that the contract is violated.

jfraney

You are right. Exception are thrown to indicate contract violation. This is exceptional situation. If you are going to open a file, then you expect it to exist (otherwise you check that with exists() method, don’t you). When this expectation fails, that is exception situation to you.

And yes, exceptional situation should occur at low enough frequency in normal cause of action.

iNikem

Exceptions should be used to deal with exceptional situations, not to implement ‘standard’ control flow.

frank

I’m places where exceptions are unavoidable, override fillInStackTrace to make it a noop. Better still, avoid the object overhead completely like this example:

https://github.com/tinkerpop/pipes/pull/70

Its a reasonably well known problem, the Oracle JVM performs an optimisation that replaces common exception types with a no-message, no-stack trace version. Search for OmitStackTraceInFastThrow for details.

Danny

Honestly, that JVM optimisation seems even more weird for me. The last thing I want to see in my logs is exception without stacktrace.

When one thinks that he MUST use exceptions for control flow, I think it is time to review one’s design :)

iNikem

Don’t forget that exceptions are imperative and not referentially transparent. They are hard to reason about, particularly when thrown exceptions are caught in different places in the call stack.

Mark Perry

Can't figure out what causes your OutOfMemoryError? Read more

Latest
Recommended
You cannot predict the way you die
When debugging a situation where systems are failing due to the lack of resources, you can no longer count on anything. Seemingly unrelated changes can trigger completely different messages and control flows within the JVM. Read more
Tuning GC - it does not have to be that hard
Solving GC pauses is a complex task. If you do not believe our words, check out the recent LinkedIn experience in garbage collection optimization. It is a complex and tedious task, so we are glad to report we have a whole lot simpler solution in mind Read more
Building a nirvana
We have invested a lot into our continuous integration / delivery infrastructure. As of now we can say that the Jenkins-orchestrated gang consisting of Ansible, Vagrant, Gradle, LiveRebel and TestNG is something an engineer can call a nirvana. Read more
Creative way to handle OutOfMemoryError
Wish to spend a day troubleshooting? Or make enemies among sysops? Registering pkill java to OutOfMemoryError events is one darn good way to achieve those goals. Read more