Home » Primitives vs. Autoboxing Speed Test

Primitives vs. Autoboxing Speed Test

Posted on May 27th, 2007 in The Art of Computer Programming by Jorge Luis

What executes faster, a calculation involving unwrapped primitives or one that uses autoboxed values? The answer is not as straight-forward as you might expect.

A quick review.

Autoboxing is the automatic wrapping of primitive values into their object-oriented class wrappers, a convenience feature introduced with Java 5. So, whereas before Java 5, you’d have to write code such as this
Double pi = new Double(3.14);
with autoboxing, the wrapping of a double primitive into its wrapper class (right-hand side) is done for you, i.e.,
Double pi = 3.14;
is equivalent to the above expression.

However, in this expression
double pi = 3.14;
the right hand side remains a primitive double.

Autounboxing is also provided, converting a wrapped value into its primitive form.
double pi = new Double(3.14);
A hypothesis

Since autoboxed expressions imply the creation of an object behind the scenes, you might expect that the overhead of object creation would make them execute more slowly than expressions involving primitive values only. In fact, chapter 1 of Java 2 v5.0 Tiger New Features warns that abusing the autoboxing feature can lead to performance degradation. A simple unit test should demonstrate this expectation.

We will race two implementations of the hypotenuse formula, one implemented with primitive doubles, the other with autoboxed doubles:

package com.jluix.lab.unit.learning.autoboxing;

public class AutoboxDouble
    {
    public double hypotenusePrimitive()
        {
        return Math.sqrt(this.a * this.a + this.b * this.b);
        }

    public Double hypotenuseBoxed()
        {
        return Math.sqrt(this.aBoxed * this.aBoxed + this.bBoxed * this.bBoxed);
        }

    public AutoboxDouble(double a, double b)
        {
        this.a = a;
        this.b = b;
        this.aBoxed = a;
        this.bBoxed = b;
        }

    private double a;
    private double b;
    private Double aBoxed;
    private Double bBoxed;
    }

The signature for Math.sqrt is public double java.lang.Math.sqrt(double value). It takes and returns a primitive double. That means hypotenuseBoxed involves both autounboxing and autoboxing.

The expression

this.aBoxed*this.aBoxed + this.bBoxed*this.bBoxed

has to evaluate to a primitive double. So the wrapped Double aBoxed and Double aBoxed are coverted to their primitive forms. Furthermore, since hypotenuseBoxed() returns Double, the primitive value returned by

return Math.sqrt(this.aBoxed*this.aBoxed + this.bBoxed*this.bBoxed);

is autoboxed into its wrapper form. If all goes well, this should turn out to be a pretty inefficient implementation of the hypotenuse formula.

double hypotenuseUnboxed() involves nothing but simple primitive doubles and does not carry any object-creation overhead, theoretically making it faster than its autoboxing counterpart.

Off to the races!

The following unit test executes both hypotenuse calculations and compares their execution times, asserting that the primitive calculation should run faster.

package com.jluix.lab.unit.learning.autoboxing;

import junit.framework.TestCase;

/**
 * Demonstrates autoboxing and autounboxing doubles.
 * Based on discussion in ch 1 of Java 2 v5.0 Tiger New Features, by Herber Schildt
 */
public class TestAutoboxDouble extends TestCase
    {
    /**
     * Executes autoboxed/autounboxed and primitive-only calculations and compares their execution times.
     */
    public void testPrimitivesFasterThanAutoboxedWrappers()
        {
        long boxedTime     = runBoxed();
        long primitiveTime = runPrimitive();
        assertRunTime(boxedTime, primitiveTime);
        }

    /**
     * Executes hypotenuse implemntation that involves autoboxing and autounboxing the triangle sides.
     * @return execution time
     */
    private long runBoxed()
        {
        long startBoxed = System.nanoTime();
        this.autoboxDouble.hypotenuseBoxed();
        long boxedTime = System.nanoTime() - startBoxed;
        return boxedTime;
        }

    /**
     * Executes hypotenuse implementation that involves primitive double triangle sides.
     * @return execution time
     */
    private long runPrimitive()
        {
        long startPrimitive = System.nanoTime();
        this.autoboxDouble.hypotenusePrimitive();
        long primitiveTime = System.nanoTime() - startPrimitive;
        return primitiveTime;
        }

    /**
     * Asserts that the excecution time for an autoboxed/autounboxed calculation is slower than the execution time for
     * a calcuation involving primitives only.
     *
     * @param boxedTime execution time of autoboxed/autounboxed calculation
     * @param primitiveTime execution time of primitive calculation
     */
    private void assertRunTime(long boxedTime, long primitiveTime)
        {
        System.out.println("(boxed time: " + boxedTime + " ns), (primitive time: " + primitiveTime + " ns)");
        assertTrue("Autoboxed/unboxed calculation (" + boxedTime +
                   ") should run slower than purely primitive calculation (" + primitiveTime + ").",
                   primitiveTime < boxedTime);
        }

    public TestAutoboxDouble(String name)
        {
        super(name);
        }

    /**
     * Set up the fixture before every test method call.
     */
    protected void setUp() throws Exception
        {
        this.autoboxDouble = new AutoboxDouble(5,7);
        }

    /**
     * Tear down the fixture after every test method call.
     */
    protected void tearDown() throws Exception
        {
        this.autoboxDouble = null;
        }

    private AutoboxDouble autoboxDouble;
    }

As expected, the primitive calculation runs much faster than the autoboxed calculation, 15.5 times faster, 2000 ns and 31,000 ns, respectively.

Primitives faster than wrappers?

But of course, that’s not the end of the story. You may have noticed that the autoboxed calculation in this unit test runs before the primitive calculation.


long boxedTime = runBoxed();
long primitiveTime = runPrimitive();

What happens if we simply reverse the call order, as does the unit test below? Would you expect a different result? If you had to wager a guess, on which side would you bet?

   /**
     * Executes autoboxed/autounboxed and primitive-only calculations and compares their execution times.  Runs the
     * primitive calculation first.
     */
    public void testPrimitivesFasterThanAutoboxedWrappersRunPrimitivesFirst()
        {
        long primitiveTime = runPrimitive();
        long boxedTime     = runBoxed();
        assertRunTime(boxedTime, primitiveTime);
        }

Surprisingly, this simple change causes the autoboxed implementation to run faster, 4000 ns and 20,000 ns or 5 times faster. This happens consitently, test run after test run.

Primitives first

Perhaps the run-time is optimizing these almost identical implementations, maybe using the same bytecode for both. But that’s just a guess. If it were the case, you’d think the times would be identical.

To try and shed some light into this behavior, let’s see what happens when the primitive calculation is run again, a second time, as in

    /**
     * What's going on in testPrimitivesFasterThanAutoboxedWrappersRunPrimitivesFirst?  Some sort of vm
     * optimization?
     */
    public void testPrimitivesFasterThanAutoboxedWrappersRunPrimitivesFirstRepeat()
        {
        long primitiveTime = runPrimitive();
        long boxedTime     = runBoxed();
             primitiveTime = runPrimitive();
        assertRunTime(boxedTime, primitiveTime);
        }

Once more, the primitive calculation wins out, consistently over many test runs.

primitiveboxedprimitive.jpg

So, what would happen if both calculations are executed multiple times in one test run? This test runs them 100 times and then compares their execution times.

 public void testPrimitivesFasterThanAutoboxedWrappersRunPrimitivesFirstLoop()
        {
        long primitiveTime = 0;
        long boxedTime   = 0;
        for (int i=0; i<100; i++)
            {
            primitiveTime = runPrimitive();
            boxedTime   = runBoxed();
            }
        assertRunTime(boxedTime, primitiveTime);
        }

Results vary over test runs. Here’s one sequence of 10 runs.

Run Boxed Time (ns) Primitive Time (ns)
1 1000 0
2 1000 0
3 1000 0
4 2000 0
5 1000 0
6 1000 1000
7 1000 0
8 1000 1000
9 1000 1000
10 1000 0

It looks like the vm may optimize, but not every time. The same time behavior occurs when the calculations are run using different triangle sides, as in

public void testPrimitivesFasterThanAutoboxedWrappersRunPrimitivesFirstLoopDiffFixtures()
        {
        long primitiveTime = 0;
        long boxedTime     = 0;
        for (int i=0; i<100; i++)
            {
            primitiveTime = runPrimitive();
            boxedTime   = runBoxed();
            this.autoboxDouble = new AutoboxDouble(i*2, i*5);
            }
        assertRunTime(boxedTime, primitiveTime);
        }

Putting all these unit tests together, this is what it looks like when all unit tests are run.

All 1

All 2

All 3

All 4

If you repeat the tests enough times, you’ll see all combinations, primitive calculations taking less, the same, and more time than the autoboxed counterpart.

Conclusion

So what is the answer to the original question? Are primitives always faster than autoboxed values? This brief experiment demonstrates that a good deal of the time they are faster. However, the ultimate answer seems to depend on what optimizations the vm performs at run time.

Full source code

AutoboxDouble.java

package com.jluix.lab.unit.learning.autoboxing;

/**
 * User: jorge
 * Date: Apr 29, 2007
 * Time: 9:37:08 PM
 */
public class AutoboxDouble
    {
    public double hypotenusePrimitive()
        {
        return Math.sqrt(this.a * this.a + this.b * this.b);
        }

    public Double hypotenuseBoxed()
        {
        return Math.sqrt(this.aBoxed * this.aBoxed + this.bBoxed * this.bBoxed);
        }

    public AutoboxDouble(double a, double b)
        {
        this.a = a;
        this.b = b;
        this.aBoxed = a;
        this.bBoxed = b;
        }

    private double a;
    private double b;
    private Double aBoxed;
    private Double bBoxed;
    }

TestAutoboxDouble.java

/**
 * User: jorge
 * Date: Apr 29, 2007
 * Time: 9:35:35 PM
 */
package com.jluix.lab.unit.learning.autoboxing;

import junit.framework.TestCase;

/**
 * Demonstrates autoboxing and autounboxing doubles.
 * Based on discussion in ch 1 of Java 2 v5.0 Tiger New Features, by Herber Schildt
 */
public class TestAutoboxDouble extends TestCase
    {
    public TestAutoboxDouble(String name)
        {
        super(name);
        }

    /**
     * Set up the fixture before every test method call.
     */
    protected void setUp() throws Exception
        {
        this.autoboxDouble = new AutoboxDouble(5,7);
        }

    /**
     * Tear down the fixture after every test method call.
     */
    protected void tearDown() throws Exception
        {
        this.autoboxDouble = null;
        }

    /**
     * Executes autoboxed/autounboxed and primitive-only calculations and compares their execution times.  Runs the
     * primitive calculation first.
     */
    public void testPrimitivesFasterThanAutoboxedWrappersRunPrimitivesFirst()
        {
        long primitiveTime = runPrimitive();
        long boxedTime     = runBoxed();
        assertRunTime(boxedTime, primitiveTime);
        }

    /**
     * What's going on in testPrimitivesFasterThanAutoboxedWrappersRunPrimitivesFirst?  Some sort of vm
     * optimization?
     */
    public void testPrimitivesFasterThanAutoboxedWrappersRunPrimitivesFirstRepeat()
        {
        long primitiveTime = runPrimitive();
        long boxedTime     = runBoxed();
             primitiveTime = runPrimitive();
        assertRunTime(boxedTime, primitiveTime);
        }

    /**
     *  What would happen if both calculations are executed multiple times in one test run?  This test runs them 100
     *  times then compares their execution times.
     */
    public void testPrimitivesFasterThanAutoboxedWrappersRunPrimitivesFirstLoop()
        {
        long primitiveTime = 0;
        long boxedTime   = 0;
        for (int i=0; i<100; i++)
            {
            primitiveTime = runPrimitive();
            boxedTime   = runBoxed();
            }
        assertRunTime(boxedTime, primitiveTime);
        }

    /**
     *  What would happen if both calculations are executed multiple times in one test run using different fixtures?
     *  This test runs them 100 times, creating a different triangle each time, and then compares their execution times.
     */
    public void testPrimitivesFasterThanAutoboxedWrappersRunPrimitivesFirstLoopDiffFixtures()
        {
        long primitiveTime = 0;
        long boxedTime     = 0;
        for (int i=0; i<100; i++)
            {
            primitiveTime = runPrimitive();
            boxedTime   = runBoxed();
            this.autoboxDouble = new AutoboxDouble(i*2, i*5);
            }
        assertRunTime(boxedTime, primitiveTime);
        }

    /**
     * Executes autoboxed/autounboxed and primitive-only calculations and compares their execution times.
     */
    public void testPrimitivesFasterThanAutoboxedWrappersRunWrappersFirst()
        {
        long boxedTime     = runBoxed();
        long primitiveTime = runPrimitive();
        assertRunTime(boxedTime, primitiveTime);
        }

    /**
     * Executes hypotenuse implemntation that involves autoboxing and autounboxing the triangle sides.
     * @return execution time
     */
    private long runBoxed()
        {
        long startBoxed = System.nanoTime();
        this.autoboxDouble.hypotenuseBoxed();
        long boxedTime = System.nanoTime() - startBoxed;
        return boxedTime;
        }

    /**
     * Executes hypotenuse implementation that involves primitive double triangle sides.
     * @return execution time
     */
    private long runPrimitive()
        {
        long startPrimitive = System.nanoTime();
        this.autoboxDouble.hypotenusePrimitive();
        long primitiveTime = System.nanoTime() - startPrimitive;
        return primitiveTime;
        }

    /**
     * Asserts that the excecution time for an autoboxed/autounboxed calculation is slower than the execution time for
     * a calcuation involving primitives only.
     *
     * @param boxedTime execution time of autoboxed/autounboxed calculation
     * @param primitiveTime execution time of primitive calculation
     */
    private void assertRunTime(long boxedTime, long primitiveTime)
        {
        System.out.println("(boxed time: " + boxedTime + " ns), (primitive time: " + primitiveTime + " ns)");
        assertTrue("Autoboxed/unboxed calculation (" + boxedTime +
                   ") should run slower than purely primitive calculation (" + primitiveTime + ").",
                   primitiveTime < boxedTime);
        }

    private AutoboxDouble autoboxDouble;
    }

Refactored source code

AutoboxDouble.java

package com.jluix.lab.unit.learning.autoboxing;

/**
 * User: jorge
 * Date: Apr 29, 2007
 * Time: 9:37:08 PM
 */
public class AutoboxDouble
    {
    /**
     * Computes the hypotenuse of a right triangle.  Not a good control for comparing primitive vs. autoboxing
     * performance as it relies on the Math.sqrt function which very likely performs parameter-based caching.
     */
    public static final Command HYPOTENUSE_COMMAND = new Command()
    {

    public double computePrimitive(double a, double b)
        {
        return Math.sqrt(a * a + b * b);
        }

    public Double computeBoxed(Double a, Double b)
        {
        return Math.sqrt(a * a + b * b);
        }

    public String getName()
        {
        return "HYPOTENUSE";
        }
    };

    /**
     * Computes the area of a rectangle.  Less likely to involve paramter-based caching, but still a possiblity.
     */
    public static final Command RECTANGULAR_AREA_COMMAND = new Command()
    {

    public double computePrimitive(double a, double b)
        {
        return a * b;
        }

    public Double computeBoxed(Double a, Double b)
        {
        return a * b;
        }

    public String getName()
        {
        return "RECTANGULAR_AREA";
        }
    };

    /**
     * Simply returns one of the sides passed.  Any run-time optimization involved?  Probably not.
     */
    public static final Command SINGLE_SIDE_COMMAND = new Command()
    {

    public double computePrimitive(double a, double b)
        {
        return a;
        }

    public Double computeBoxed(Double a, Double b)
        {
        return (double) a;//unbox, then re-box
        }

    public String getName()
        {
        return "SINGLE_SIDE";
        }
    };

    public double computePrimitive()
        {
        return this.command.computePrimitive(this.a, this.b);
        }

    public Double computeBoxed()
        {
        return this.command.computeBoxed(this.aBoxed, this.bBoxed);
        }

    public String getCommandName()
        {
        return this.command.getName();
        }

    public AutoboxDouble(double a, double b, Command command)
        {
        if (command == null)
            {
            throw new IllegalArgumentException("command cannot be null.");
            }
        this.command = command;

        this.a = a;
        this.b = b;
        this.aBoxed = a;
        this.bBoxed = b;
        }

    private double a;
    private double b;
    private Double aBoxed;
    private Double bBoxed;
    private Command command;
    }

TestAutoboxDouble.java

/**
 * User: jorge
 * Date: Apr 29, 2007
 * Time: 9:35:35 PM
 */
package com.jluix.lab.unit.learning.autoboxing;

import junit.framework.TestCase;

/**
 * Demonstrates autoboxing and autounboxing doubles.
 * Based on discussion in ch 1 of Java 2 v5.0 Tiger New Features, by Herber Schildt
 */
public class TestAutoboxDouble extends TestCase
    {
    public TestAutoboxDouble(String name)
        {
        super(name);
        }

    public void testPrimitivesFasterThanAutoboxedWrappersRunPrimitivesFirstHypotenuse()
        {
        this._setUp(AutoboxDouble.HYPOTENUSE_COMMAND);
        this._testPrimitivesFasterThanAutoboxedWrappersRunPrimitivesFirst();
        }

    public void testPrimitivesFasterThanAutoboxedWrappersRunPrimitivesFirstRepeatHypotenuse()
        {
        this._setUp(AutoboxDouble.HYPOTENUSE_COMMAND);
        this._testPrimitivesFasterThanAutoboxedWrappersRunPrimitivesFirstRepeat();
        }

    public void testPrimitivesFasterThanAutoboxedWrappersRunPrimitivesFirstLoopHypotenuse()
        {
        this._setUp(AutoboxDouble.HYPOTENUSE_COMMAND);
        this._testPrimitivesFasterThanAutoboxedWrappersRunPrimitivesFirstLoop();
        }

    public void testPrimitivesFasterThanAutoboxedWrappersRunPrimitivesFirstLoopDiffFixturesHypotenuse()
        {
        this._setUp(AutoboxDouble.HYPOTENUSE_COMMAND);
        this._testPrimitivesFasterThanAutoboxedWrappersRunPrimitivesFirstLoopDiffFixtures();
        }

    public void testPrimitivesFasterThanAutoboxedWrappersRunPrimitivesFirstAvoidArgCachingHypotenuse()
        {
        this._setUp(AutoboxDouble.HYPOTENUSE_COMMAND);
        this._testPrimitivesFasterThanAutoboxedWrappersRunPrimitivesFirstAvoidArgCaching();
        }

    public void testPrimitivesFasterThanAutoboxedWrappersRunWrappersFirstHypotenuse()
        {
        this._setUp(AutoboxDouble.HYPOTENUSE_COMMAND);
        this._testPrimitivesFasterThanAutoboxedWrappersRunWrappersFirst();
        }

    public void testPrimitivesFasterThanAutoboxedWrappersRunPrimitivesFirstRectangularArea()
        {
        this._setUp(AutoboxDouble.RECTANGULAR_AREA_COMMAND);
        this._testPrimitivesFasterThanAutoboxedWrappersRunPrimitivesFirst();
        }

    public void testPrimitivesFasterThanAutoboxedWrappersRunPrimitivesFirstRepeatRectangularArea()
        {
        this._setUp(AutoboxDouble.RECTANGULAR_AREA_COMMAND);
        this._testPrimitivesFasterThanAutoboxedWrappersRunPrimitivesFirstRepeat();
        }

    public void testPrimitivesFasterThanAutoboxedWrappersRunPrimitivesFirstLoopRectangularArea()
        {
        this._setUp(AutoboxDouble.RECTANGULAR_AREA_COMMAND);
        this._testPrimitivesFasterThanAutoboxedWrappersRunPrimitivesFirstLoop();
        }

    public void testPrimitivesFasterThanAutoboxedWrappersRunPrimitivesFirstLoopDiffFixturesRectangularArea()
        {
        this._setUp(AutoboxDouble.RECTANGULAR_AREA_COMMAND);
        this._testPrimitivesFasterThanAutoboxedWrappersRunPrimitivesFirstLoopDiffFixtures();
        }

    public void testPrimitivesFasterThanAutoboxedWrappersRunPrimitivesFirstAvoidArgCachingRectangularArea()
        {
        this._setUp(AutoboxDouble.RECTANGULAR_AREA_COMMAND);
        this._testPrimitivesFasterThanAutoboxedWrappersRunPrimitivesFirstAvoidArgCaching();
        }

    public void testPrimitivesFasterThanAutoboxedWrappersRunWrappersFirstRectangularArea()
        {
        this._setUp(AutoboxDouble.RECTANGULAR_AREA_COMMAND);
        this._testPrimitivesFasterThanAutoboxedWrappersRunWrappersFirst();
        }

    public void testPrimitivesFasterThanAutoboxedWrappersRunPrimitivesFirstSingleSide()
        {
        this._setUp(AutoboxDouble.SINGLE_SIDE_COMMAND);
        this._testPrimitivesFasterThanAutoboxedWrappersRunPrimitivesFirst();
        }

    public void testPrimitivesFasterThanAutoboxedWrappersRunPrimitivesFirstRepeatSingleSide()
        {
        this._setUp(AutoboxDouble.SINGLE_SIDE_COMMAND);
        this._testPrimitivesFasterThanAutoboxedWrappersRunPrimitivesFirstRepeat();
        }

    public void testPrimitivesFasterThanAutoboxedWrappersRunPrimitivesFirstLoopSingleSide()
        {
        this._setUp(AutoboxDouble.SINGLE_SIDE_COMMAND);
        this._testPrimitivesFasterThanAutoboxedWrappersRunPrimitivesFirstLoop();
        }

    public void testPrimitivesFasterThanAutoboxedWrappersRunPrimitivesFirstLoopDiffFixturesSingleSide()
        {
        this._setUp(AutoboxDouble.SINGLE_SIDE_COMMAND);
        this._testPrimitivesFasterThanAutoboxedWrappersRunPrimitivesFirstLoopDiffFixtures();
        }

    public void testPrimitivesFasterThanAutoboxedWrappersRunPrimitivesFirstAvoidArgCachingSingleSide()
        {
        this._setUp(AutoboxDouble.SINGLE_SIDE_COMMAND);
        this._testPrimitivesFasterThanAutoboxedWrappersRunPrimitivesFirstAvoidArgCaching();
        }

    public void testPrimitivesFasterThanAutoboxedWrappersRunWrappersFirstSingleSide()
        {
        this._setUp(AutoboxDouble.SINGLE_SIDE_COMMAND);
        this._testPrimitivesFasterThanAutoboxedWrappersRunWrappersFirst();
        }

    /**
     * Tear down the fixture after every test method call.
     */
    protected void tearDown()
        {
        this.autoboxDouble = null;
        this.command = null;
        }

    /**
     * Set up the fixture before every test method call.
     */
    private void _setUp(Command command)
        {
        this.a = 5;
        this.b = 7;
        this.command = command;
        this.autoboxDouble = new AutoboxDouble(this.a, this.b, this.command);
        }

    /**
     * Executes autoboxed/autounboxed and primitive-only calculations and compares their execution times.  Runs the
     * primitive calculation first.
     */
    private void _testPrimitivesFasterThanAutoboxedWrappersRunPrimitivesFirst()
        {
        long primitiveTime = runPrimitive();
        long boxedTime = runBoxed();
        assertRunTime(boxedTime, primitiveTime);
        }

    /**
     * What's going on in testPrimitivesFasterThanAutoboxedWrappersRunPrimitivesFirst?  Some sort of vm
     * optimization?
     */
    private void _testPrimitivesFasterThanAutoboxedWrappersRunPrimitivesFirstRepeat()
        {
        long primitiveTime = runPrimitive();
        long boxedTime = runBoxed();
        primitiveTime = runPrimitive();
        assertRunTime(boxedTime, primitiveTime);
        }

    /**
     * What would happen if both calculations are executed multiple times in one test run?  This test runs them 100
     * times then compares their execution times.
     */
    private void _testPrimitivesFasterThanAutoboxedWrappersRunPrimitivesFirstLoop()
        {
        long primitiveTime = 0;
        long boxedTime = 0;
        for (int i = 0; i < 100; i++)
            {
            primitiveTime = runPrimitive();
            boxedTime = runBoxed();
            }
        assertRunTime(boxedTime, primitiveTime);
        }

    /**
     * What would happen if both calculations are executed multiple times in one test run using different fixtures?
     * This test runs them 100 times, creating a different triangle each time, and then compares their execution times.
     */
    private void _testPrimitivesFasterThanAutoboxedWrappersRunPrimitivesFirstLoopDiffFixtures()
        {
        long primitiveTime = 0;
        long boxedTime = 0;
        this.autoboxDouble = new AutoboxDouble(this.a, this.b, this.command);
        for (int i = 0; i < 100; i++)
            {
            primitiveTime = runPrimitive();
            boxedTime = runBoxed();
            this.autoboxDouble = new AutoboxDouble(i * 2, i * 5, this.command);
            }
        assertRunTime(boxedTime, primitiveTime);
        }

    /**
     * Avoid possible argument-based caching by providing each implemenation with different arguments.
     */
    private void _testPrimitivesFasterThanAutoboxedWrappersRunPrimitivesFirstAvoidArgCaching()
        {
        long primitiveTime = 0;
        long boxedTime = 0;
        for (int i = 0; i < 100; i++)
            {
            this.autoboxDouble = new AutoboxDouble(i * 2, i * 5, this.command);
            primitiveTime = runPrimitive();
            this.autoboxDouble = new AutoboxDouble(i * 7, i * 4, this.command);
            boxedTime = runBoxed();
            assertRunTime(boxedTime, primitiveTime);
            }
        }

    /**
     * Executes autoboxed/autounboxed and primitive-only calculations and compares their execution times.
     */
    private void _testPrimitivesFasterThanAutoboxedWrappersRunWrappersFirst()
        {
        long boxedTime = runBoxed();
        long primitiveTime = runPrimitive();
        assertRunTime(boxedTime, primitiveTime);
        }

    /**
     * Executes calculation implementation that involves autoboxing and autounboxing the triangle sides.
     *
     * @return execution time
     */
    private long runBoxed()
        {
        long startBoxed = System.nanoTime();
        this.autoboxDouble.computeBoxed();
        long boxedTime = System.nanoTime() - startBoxed;
        return boxedTime;
        }

    /**
     * Executes calculation implementation that involves primitive double triangle sides.
     *
     * @return execution time
     */
    private long runPrimitive()
        {
        long startPrimitive = System.nanoTime();
        this.autoboxDouble.computePrimitive();
        long primitiveTime = System.nanoTime() - startPrimitive;
        return primitiveTime;
        }

    /**
     * Asserts that the excecution time for an autoboxed/autounboxed calculation is slower than the execution time for
     * a calcuation involving primitives only.
     *
     * @param boxedTime     execution time of autoboxed/autounboxed calculation
     * @param primitiveTime execution time of primitive calculation
     */
    private void assertRunTime(long boxedTime, long primitiveTime)
        {
        System.out.println(this.autoboxDouble.getCommandName() + " (boxed time: " + boxedTime + " ns), (primitive time: " + primitiveTime + " ns)");
        assertTrue("Autoboxed/unboxed calculation (" + boxedTime + ") should run slower than purely primitive calculation (" + primitiveTime + ").", primitiveTime < boxedTime);
        }

    private double a;
    private double b;
    private AutoboxDouble autoboxDouble;
    private Command command;
    }

Command.java

package com.jluix.lab.unit.learning.autoboxing;

/**
 * Provide two implementations of the same calculation, one involving primitives and the other autoboxing/autounboxing.
 *
 * @author jorge
 * @version $Revision:  $
 * @created May 29, 2007 9:14:36 PM
 * @since 1.0
 */
public interface Command
    {
    double computePrimitive(double a, double b);

    Double computeBoxed(Double a, Double b);

    String getName();
    }

One Response to 'Primitives vs. Autoboxing Speed Test'

Subscribe to comments with RSS or TrackBack to 'Primitives vs. Autoboxing Speed Test'.

  1. John Hume said,

    on June 1st, 2007 at 9:16 am

    Have you tried decompiling your classes to see if the compiler did what you expect in the autoboxing case? (I believe the bytecode generated from autoboxing is indistinguishable from bytecode compiled from manual boxing and unboxing, so a decompiler will show you all the object creation and method calls hidden behind Java5’s syntax.)

    The one conclusive takeaway from this is to be wary of microbenchmarks.

Leave a comment