The Source for Java Technology Collaboration
User: Password:



Scott Oaks's Blog

Switching tracks

Posted by sdo on July 09, 2007 at 02:06 PM | Comments (2)

One of those lesser-known features of Java is that it contains two different bytecodes for switch statements: a generic switch statement, and an (allegedly more optimal) table-driven switch statement. The compiler will automatically generate one or the other of these statements depending on the values in the switch statement: the table-driven statement is used when the switch values are close to being sequential (possibly with a few gaps), where the generic statement is used in all other cases. It's the sort of thing that intrigues performance-oriented developers: is the table-driven statement really more optimal? Is it worth coercing the variable involved in a switch statement so that the compiler can generate a table-driven statement? Is there ever a case in a real-world program where this would even matter? Interesting questions, but since I assumed the answer to the last one was "no", I never really thought about the first few.

Now, however, I'm looking at some profiles of Glassfish V2, and I find that when running a particular application, we're spending a full 1% of our time in this method:
protected java.util.logging.Level convertLevel(int level) {
int index = level / 100;
switch (index) {
case 3: return Level.FINEST;
case 4: return Level.FINER;
case 5: return Level.FINE;
case 7: return Level.CONFIG;
case 8: return Level.INFO;
case 9: return Level.WARNING;
case 10: return Level.SEVERE;
default: return Level.FINER;
}
}
Seems like a pretty simple method to be spending so much time in (and let's face it, sampling profilers may overstate their time for a method like this). So I dug in a little further. The level value passed to this method is always exactly divisible by 100: it's not the case that level can be 300, 305, and 310. So there is a one-to-one correspondence between the integers passed to the method and the Level object returned. So I was rather impressed that the original author of this code had known enough arcane Java trivia to know that he could coerce the argument to get the table-driven switch statement.

Alas, if only he'd taken the next step to see if the performance difference was worthwhile. It turns out that it wasn't: removing the division from this method and recasting the switch statement to values of 300, 400, and so on eliminted all the time the profiler attributed to this method and resulted in a .5% improvement in the way the application ran. I also did some quick micro-benchmarking of the method and discovered that if I didn't need to coerce the argument into the switch statement (that is, if I passed in values of 3, 4, 5, etc. to begin with), the perfomance of the method was essentially the same, but adding the division statement to coerce the argument slowed down execution of the method quite significantly.

At .5% of performance, I'm not sure that this is the real-world example of where this would ever matter -- though when you provide a platform for other people's applications, you worry about your operations being as streamlined as possible. But it is another example of why you should test you code before making assumptions about how it will perform, and particularly before writing code to work around a potential performance issue.

Bookmark blog post: del.icio.us del.icio.us Digg Digg DZone DZone Furl Furl Reddit Reddit
Comments
Comments are listed in date ascending order (oldest first) | Post Comment

  • It appears from the figures you've given that the cost of the division was about the same as the cost of the switch, so hardly surprising that the type of switch used didn't really show any difference.

    Even if the table driven switch was 10% faster, you would only see 0.5% versus 0.55%, which is a difference probably not even noticeable in normal profiling.

    You might also want to reconsider the whole call stack design here. According to these figures a single division in the code was called so frequently that it took 0.5% of the time spent in the app. Either the benchmark was wrong, or this method is called way too often.

    Posted by: jacksjpt on July 29, 2007 at 05:31 AM

  • It appears from the figures you've given that the cost of the division was about the same as the cost of the switch, so hardly surprising that the type of switch used didn't really show any difference.

    But that's the crux of the issue: never assume that "optimizing" code to take advantage of something will give you a performance gain: you should code effectively, measure your performance, and then think about optimizations like this.


    Either the benchmark was wrong, or this method is called way too often.

    Clearly, the method is called too often -- which is what led me into this investigation :-)

    Posted by: sdo on July 30, 2007 at 07:33 AM



Only logged in users may post comments. Login Here.


Powered by
Movable Type 3.01D
 Feed java.net RSS Feeds