If you use Exceptions for path control, dont fill in the stack trace

This post contains general assertions about code performance and readability. Every such assertion every made in these areas can be easily disproved by a thought experiment, contrived test case or hyperbolic arguments. That said I think I am mostly right ?

Exceptions Are Slow

Throwing exceptions is bad right. Its slow and makes the code unreadable. Well… kinda…maybe..

Webwork 1 has a flat configuration mechanism to look up names to values.

Its like a chained hash map of configuration providers that are asked in turn “do you know about this key” and if yes what is its value. And if no, tell me that and I will go onto the next one. Its classic tri-state return logic

And it uses exceptions as the mechanism to say “I dont know about that key”. Now before you snicker at this, remember that java.util.ResourceBundle does exactly this. At least it did up until 1.6 but thats another story.

Here is some code for example:

public Object getImpl(final String aName) throws IllegalArgumentException
{
  // Delegate to the other configurations
  IllegalArgumentException e = null;
  for (final ConfigurationInterface config : configList)
  {
    try{
       return config.getImpl(aName);
    }catch (final IllegalArgumentException ex){
       e = ex;
      // Try next config
    }
  }
  throw e;
}

In this example the stack trace of the signalling exception is not important. And I would also make the argument that this is very readable code from an exception handling point of view. I might have declared my own specific runtime exception type rather than use IllegalArgumentException but its very readable.

And even the fact that the last exception out is rethrown in not important becuse most of the surrounding code does this.

try{
        String classname = (String) Configuration.get(CLASS_NAME);
        if (classname != null && classname.length() > 0)
        {
            try {
                impl = (InjectionImpl) ClassLoaderUtils.loadClass(classname, InjectionUtils.class).newInstance();
            } catch (Exception e)
            {
                LogFactory.getLog(InjectionUtils.class).error("Could not load class "  + classname + " or could not cast it to InjectionUtils. Using default", e);
            }
        }
    }catch (IllegalArgumentException e){
       //do nothing - this is just because the property couldn't be found.
    }

So we don’t need the stack trace.But you get it always because Throwable, as the root of all exceptions, does this in all of its constructors:

public Throwable() {
fillInStackTrace();
}
public Throwable(String message) {
fillInStackTrace();
detailMessage = message;
}
public Throwable(String message, Throwable cause) {
fillInStackTrace();
detailMessage = message;
this.cause = cause;
}

Notice how every incarnation fills in the stack trace?However Jed Wesley Smith led us onto a great Java trick. Just dont fill in the stack trace.

Speed Of Execution

Filling in exception stack traces is what takes all the time in exception handling.

Without it then :

try {
throw e;
...
} catch (e) {
}

is pretty much an object allocation and a goto statement.

And its simple not to fill out the stack trace.

In fact Jed had already left an example in Jira source code

/**
* efficient exception that has no stacktrace; we use this for flow-control.
*/
@Immutable
static final class ResourceNotFound extends Exception
{
ResourceNotFound()
{}
@Override
public Throwable fillInStackTrace()
{
return this;
}
}

Thats it. No stack trace is available on this exception. If you e.printStackTrace() it is just empty.

I wrote a micro test on this comparing thrown stack traced filled exceptions against stack trace empty exceptions, at different levels of stack depth.

I found that it is 25% faster. But because I am a fail at maths, I was correctly told that its in fact a 400% increase.

Having no stack trace is FAST.

Rupert Shuttleworth initially found this hot spot while profiling Jira during the functional test runs during dev speed week. He found that 5% of the server time was taking up by this exception handling.

There are many calls to find config per request and each delegates down a stack of about 10 config providers.

Once he used an exception class that did not fill in the stack trace, the code fell off the profiling list. Win!!

Readability Of The Code

You could imagine returning a tuple object in Java that indicate the tri-state return logic.

Something more like this:

public static class CompundResult {
private final Object value;
private final boolean found;
public CompoundResult(final Object value, final boolen found)
{
this.value = value;
this.found = found;
}
public boolean found()
{
return found;
}
public boolean value()
{
return value;
}
}
public CompundResult getImpl(final String aName)
{
// Delegate to the other configurations
for (final ConfigurationInterface config : configList)
{
CompundResult result = config.getImpl(aName);
if (result.wasFound())
{
return result;
}
// Try next config
}
return new CompoundResult(null,fase);
}


But I think this is slightly less readable and also requires an intermediatory class for the compound return object. I argue that the exceptions is more readable. I initial didnt think that but I think I was coloured by my perceptions that exceptions are slow and therefore bad.

But if the exceptions cost you next to nothing then it some how becomes more elegant code.

Also in this case I was hamstrung by an existing design, so changing all the callers was going to be hard.

Jed has since shown me a great use of functional style Options to better encode the compound object shown above. But lets leave that for another blog post.

TL;DR Too Late

So the lesson is that you if you use exceptions for path control, make sure you use an exception class that does not fill out its stack trace.

It will be faster and more readable.

Thanks to Jed for the tip and especially to Rupert for finding the hotspot in the first place.

发布了477 篇原创文章 · 获赞 588 · 访问量 267万+

猜你喜欢

转载自blog.csdn.net/qq_15037231/article/details/89421586