Friday, June 24, 2011

new vs newInstance() performace

Everybody knows (or should) that any operation performed using reflections is bound to be slower than its static, compiled counterpart. But as jdk versions are increasing, this time is decreasing.

I was making the classic micro-benchmarking error of not warming up the VM, so here’s the slightly modified version of the code:

public class TheCostOfReflection {
 
    public static void main(String[] args) throws Exception {
        int nObjects = 100;
        Object[] objects = new Object[nObjects];
 
        // warm up a bit
        int warmupLoops = Math.min(nObjects, 100);
        for (int i = 0; i < warmupLoops; i++) {
            testNewOperator(nObjects, objects);
            testNewInstance(nObjects, objects);
        }
 
        System.gc();
 
        // using new
        System.out.println("Testing 'new'...");
        long newTime = testNewOperator(nObjects, objects);
        System.out.println("Took " + (newTime / 1000000f) + "ms to create " +
                           nObjects + " objects via 'new' (" +
                           (nObjects / (newTime / 1000000f)) +
                           " objects per ms)");
        System.gc();
        // using newInstance() on class
        System.out.println("Testing 'newInstance()'...");
        long niTime = testNewInstance(nObjects, objects);
        System.out.println("Took " + (niTime / 1000000f) + "ms to create " +
                           nObjects + " objects via reflections (" +
                           (nObjects / (niTime / 1000000f)) +
                           " objects per ms)");
        // ratio
        System.out.println("'new' is " + (niTime / (float) newTime) +
                           " times faster than 'newInstance()'.");
    }
 
    private static long testNewInstance(int nObjects, Object[] objects)
            throws Exception {
        long start = System.nanoTime();
        for (int i = 0; i < nObjects; i++) {
            objects[i] = Object.class.newInstance();
        }
        return System.nanoTime() - start;
    }
 
    private static long testNewOperator(int nObjects, Object[] objects) {
        long start = System.nanoTime();
        for (int i = 0; i < nObjects; i++) {
            objects[i] = new Object();
        }
        return System.nanoTime() - start;
    }
}

Running this with -Xms512m -Xmx512m* yields the following result:

Testing 'new'...
Took 7.610116ms to create 1000000 objects via 'new' (131404.05 objects per ms)
Testing 'newInstance()'...
Took 184.72641ms to create 1000000 objects via reflections (5413.41 objects per ms)
'new' is 24.273798 times faster than 'newInstance()'.

Nearly 25x slower. If you lower the value of nObjects the difference gets smaller, though.
Testing 'new'...
Took 0.002794ms to create 100 objects via 'new' (35790.98 objects per ms)
Testing 'newInstance()'...
Took 0.021581ms to create 100 objects via reflections (4633.7056 objects per ms)
'new' is 7.7240515 times faster than 'newInstance()'.

Adding a -server flag to the VM parameters did the trick, though.
Testing 'new'...
Took 8.862299ms to create 1000000 objects via 'new' (112837.54 objects per ms)
Testing 'newInstance()'...
Took 13.91287ms to create 1000000 objects via reflections (71875.88 objects per ms)
'new' is 1.5698942 times faster than 'newInstance()'.

And for 100 object instantiations:
Testing 'new'...
Took 0.002864ms to create 100 objects via 'new' (34916.20 objects per ms)
Testing 'newInstance()'...
Took 0.006007ms to create 100 objects via reflections (16647.24 objects per ms)
'new' is 2.0974162 times faster than 'newInstance()'.

When the JIT magic kicks in, you get some SERIOUS improvements.
* NOTE: 512m to avoid garbage collection interference. You can run with -XX:+PrintGCDetails to ensure that the GC isn’t acting during the instantiation loops. You should see two full GC passages though, because of lines 14 and 23.
I did run the tests a dozen times for each test; the outputs posted fall within the averages.
Tip of the day: Even though I got far better results after introducing the warmup phase, I still suggest you use the good ol’ Factory pattern. Unless you’re creating a few dozen instances in a non-critical zones of your code, the factory is the way to go, as newInstance() can never (?) get as fast as new.


No comments:

Post a Comment

Chitika