The following code measure the time it takes for 100 invocations of the method handle(Object o) from the interface Handler (Yes it’s bad quality profiling):
package test;
import java.util.LinkedList;
public class Test {
static int i = 0;
private interface Handler {
public void handle(Object o);
}
private static class SuperHandler implements Handler {
public void handle(Object o) { i += 1; }
}
private static class NoSuperHandler implements Handler {
public void handle(Object o) { i += 1; }
}
private static class LulSuperHandler implements Handler {
public void handle(Object o) { i += 1; }
}
private static class LilSuperHandler implements Handler {
public void handle(Object o) { i += 1; }
}
private static class LolSuperHandler implements Handler {
public void handle(Object o) { i += 1; }
}
private static class LalSuperHandler implements Handler {
public void handle(Object o) { i += 1; }
}
private static class LylSuperHandler implements Handler {
public void handle(Object o) { i += 1; }
}
private static class LzlSuperHandler implements Handler {
public void handle(Object o) { i += 1; }
}
public static void main(String[] args) {
LinkedList<Handler> ll = new LinkedList<Handler>();
for(int j = 0; j < 100; j++) {
if((j % 8) == 0) ll.add(new SuperHandler());
if((j % 8) == 1) ll.add(new NoSuperHandler());
if((j % 8) == 2) ll.add(new LulSuperHandler());
if((j % 8) == 3) ll.add(new LilSuperHandler());
if((j % 8) == 4) ll.add(new LolSuperHandler());
if((j % 8) == 5) ll.add(new LalSuperHandler());
if((j % 8) == 6) ll.add(new LylSuperHandler());
if((j % 8) == 7) ll.add(new LzlSuperHandler());
}
long begin = System.currentTimeMillis();
for(int j = 0; j < 1000000; j++) for(Handler h: ll) h.handle(null);
System.out.println("time in ms: " + (System.currentTimeMillis() - begin));
System.out.println("i: " + i);
}
}
The fact is that if the LinkedList contains only one kind of Handler, for example SuperHandler, the execution time is smaller than if they were 2, 3, etc different kinds of Handler. And every time I add a new kind of Handler in the list, the performances decreases.
For example when I change only this part I get better performances than above:
for(int j = 0; j < 100; j++) {
if((j % 2) == 0) ll.add(new SuperHandler());
if((j % 2) == 1) ll.add(new NoSuperHandler());
}
Is there a special optimization operating here ? What in the JAVA architecture does the performances decrease ? Is my test wrong because the unused Handler are “removed”, or “hidden” by the compiler ?
(I’m using Linux Ubuntu – JAVA 1.7, from Oracle)
Yes. Hotspot is very clever about how it handles virtual methods. If there are only a couple of implementations of an interface, and those implementations are small, it can avoid a full vtable-lookup by just checking for the right type, and inlining the code.
By the time you’ve got several different implementations, it goes back to the vtable implementation. (Hotspot is clever enough to undo optimizations which are no longer practical. I’m shocked that it all hangs together, but apparently it does.)
Note that this isn’t a matter of how many different classes are in the list – there’s rather more going on here. See Peter’s answer for details.