|
Re: Musing about Closures
|
Posted: Oct 10, 2005 12:23 AM
|
|
I thought that relying on two non-independent assessments of delegates and innerclasses, one from Sun the other from MS, was a bit dodgy so I wrote a quick benchmark.
package innerclasses;
public class Main {
interface Timeable< R > { public R time(); }
static < R > void timeOnce( final Timeable< R > t ) {
System.gc();
System.gc();
final long start = System.currentTimeMillis();
final R r = t.time();
final long finish = System.currentTimeMillis();
System.out.println( "time taken = " + (finish - start) + " ms" + ", result = " + r );
}
static < R > void time( final Timeable< R > t ) {
System.out.println( t );
System.out.print( "run 1: " );
timeOnce( t );
System.out.print( "run 2: " );
timeOnce( t );
}
static final long times = 1000000000; /* times round loop for timing */
static class LongHolder { long v; }
void inc( final LongHolder l ) { l.v++; } // delegate method
interface Inner { void inc(); } // adaptor interface to give the method a name
public static void main( final String[] notUsed ) {
time( new Timeable< Long >() {
public String toString() { return "Delegate - as optimum as possible since method names match"; }
public Long time() {
final Main delegate = new Main(); // delegate method name not needed in this example since it matches already - see comment below
final LongHolder l = new LongHolder();
l.v = 0L;
for ( long count = 1; count < times; count++ ) delegate.inc( l ); // in a real delegate call the name would not have to be the same - just the signature
return l.v;
}
} );
time( new Timeable< Long >() {
public String toString() { return "Inner class"; }
public Long time() {
final LongHolder l = new LongHolder();
l.v = 0L;
final Inner inner = new Inner() { public void inc() { l.v++; } }; // inner class
for ( long count = 1; count < times; count++ ) inner.inc();
return l.v;
}
} );
time( new Timeable< Long >() {
public String toString() { return "No delegate or inner class, but still heap allocation"; }
public Long time() {
final LongHolder l = new LongHolder();
l.v = 0L;
for ( long count = 1; count < times; count++ ) l.v++;
return l.v;
}
} );
time( new Timeable< Long >() {
public String toString() { return "No delegate, no inner class, and no heap allocation - baseline comparison"; }
public Long time() {
long l = 0L;
for ( long count = 1; count < times; count++ ) l++;
return l;
}
} );
}
}
That gave the following:
Delegate - as optimum as possible since method names match run 1: time taken = 10656 ms, result = 999999999 run 2: time taken = 6439 ms, result = 999999999 Inner class run 1: time taken = 8072 ms, result = 999999999 run 2: time taken = 5678 ms, result = 999999999 No delegate or inner class, but still heap allocation run 1: time taken = 5668 ms, result = 999999999 run 2: time taken = 6479 ms, result = 999999999 No delegate, no inner class, and no heap allocation - baseline comparison run 1: time taken = 4857 ms, result = 999999999 run 2: time taken = 4857 ms, result = 999999999
Which as you can see gives much the same result for all cases. Therefore I side with Sun; there is no significant difference in execution time between a delegate and an inner class, or for that matter an inner class and directly coding. My guess is the Sun server JVM, the one I used, inlines the code in all cases - hence no difference.
The only slight difference in the above test is when heap allocation is avoided, the last test. The next version of Java, 6, has escape analysis that will stack allocate where possible, so presumably all tests using J6 would be the same.
|
|