Switching tracks
Posted by sdo on July 9, 2007 at 5:06 PM EDT
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.
Related Topics >>
Blog Links >>
- Login or register to post comments
- Printer-friendly version
- sdo's blog
- 993 reads





