5

I'm currently working on my Bachelor Thesis about how to write effective Java code. The following four code snippets are part of a JMH benchmark which will execute every method 1 million times each.

public final static int primitiveOnly(int dummy, int add1, int add2) {
    for(int i = 0; i < 10; i++) {
        dummy += (add1 + add2);
    }
    return dummy;
}
public final static int primitiveToWrapper(int dummy, int add1, Integer add2) {
    for(int i = 0; i < 10; i++) {
        dummy += (add1 + add2);
    }
    return dummy;
}
public final static int wrapperToPrimitive(Integer dummy, Integer add1, int add2) {
    for(int i = 0; i < 10; i++) {
        dummy += (add1 + add2);
    }
    return dummy;
}
public final static Integer wrapperToWrapper(Integer dummy, Integer add1, Integer add2) {
    for(int i = 0; i < 10; i++) {
        dummy += (add1 + add2);
    }
    return dummy;
}

The results for this four methods are:

  • primitiveOnly: 1.7 ms/operation
  • primitiveToWrapper: 2.2 ms/operation
  • wrapperToPrimitive: 47.5 ms/operation
  • wrapperToWrapper: 48.2 ms/operation

The reason for this behaviour would be that during the operation in primitiveToWrapper the Integer value has just to be unboxed where in the operation in wrapperToPrimitive has to box the first operand into an Integer which results in an expensive object creation.

Is there a specific reason Java behaves like this? I read through the The Java Language Specification but wasn't able to find a answer to this question.

UPDATE:

To address the point regarding the return values (thanks to Phil Anderson) i updated my code. In addition i changed all the Integer variables in the benchmark class to int. This is the new version:

public final static int primitiveOnly(int dummy, int add1, int add2) {
    for(int i = 0; i < 10; i++) {
        dummy += (add1 + add2);
    }
    return dummy;
}

public final static int primitiveToWrapperIntDummy(int dummy, int add1, Integer add2) {
    for(int i = 0; i < 10; i++) {
        dummy += (add1 + add2);
    }
    return dummy;
}

public final static Integer primitiveToWrapperIntegerDummy(Integer dummy, int add1, Integer add2) {
    for(int i = 0; i < 10; i++) {
        dummy += (add1 + add2);
    }
    return dummy;
}

public final static int wrapperToPrimitiveIntDummy(int dummy, Integer add1, int add2) {
    for(int i = 0; i < 10; i++) {
        dummy += (add1 + add2);
    }
    return dummy;
}

public final static Integer wrapperToPrimitiveIntegerDummy(Integer dummy, Integer add1, int add2) {
    for(int i = 0; i < 10; i++) {
        dummy += (add1 + add2);
    }
    return dummy;
}

public final static int wrapperToWrapperIntDummy(int dummy, Integer add1, Integer add2) {
    for(int i = 0; i < 10; i++) {
        dummy += (add1 + add2);
    }
    return dummy;
}

public final static Integer wrapperToWrapperIntegerDummy(Integer dummy, Integer add1, Integer add2) {
    for(int i = 0; i < 10; i++) {
        dummy += (add1 + add2);
    }
    return dummy;
}

The results are an average of 10 iterations (1 iteration = 1 million executions of each method above).

  • primitiveOnly: 0.783
  • primitiveToWrapper (int dummy): 0.735
  • primitiveToWrapper (Integer dummy): 24.999
  • WrapperToPrimitive (int dummy): 0.709
  • WrapperToPrimitive (Integer dummy): 26.782
  • WrapperToWrapper (int dummy): 0.764
  • WrapperToWrapper (Integer dummy): 27.301

The final results now feel much more intuitive. Thanks everyone for helping me out.

whatTheFox
  • 118
  • 1
  • 8
  • The whole benchmark contains a total of 4 methods for every possible combination of int and Integer. I'll edit it in. – whatTheFox Jun 01 '15 at 12:58
  • 1
    Note that even with JMH, the results should be taken with a grain of salt. Due to the fixed loop size and "dummyness" of the operations, you hardly ever know what the JIT will actually make out of this. – Marco13 Jun 01 '15 at 13:36

2 Answers2

3

In the second bit of code, every time you assign a value to dummy java has to box it into an Integer because that's the type of the varaible. It doesn't know that you never actually call any methods on it and that it could be a simple int.

So each time it hits the code dummy += (add1 + add2); it has to do the following.

  • Unbox dummy
  • Perform the additions
  • Box the result back into dummy

It will do this each time through the for loop.

Phil Anderson
  • 3,146
  • 13
  • 24
  • True, but since the datatypes are clear from the start, why doesn't Java just switch the operands and perform a simple unboxing operation instead of the expensive boxing? Should be possible since the addition is commutative and each variable in a statement is evaluated before the operation takes place. – whatTheFox Jun 01 '15 at 13:10
  • 1
    I'm not sure I understand your point. Ultimately, it's assigning an int to dummy. As dummy is of type Integer, it has to box it. There's no way to avoid that. – Phil Anderson Jun 01 '15 at 13:14
  • Point taken. I just rewrote some of the code to address the boxing operation of the dummy. Adding an Integer to an int is still slower than the other way round... I'll add the new code and results to my question. – whatTheFox Jun 01 '15 at 13:32
2

This is because when dummy is an Integer, and its value is immutable. See e.g. Why are Integers immutable in Java?

Basically, in the last method when you write dummy += (add1 + add2);, it means

dummy = Integer.valueOf(dummy.intValue() + add1.intValue() + add2.intValue());

Every time in the loop, a new object needs to be allocated to keep a new integer value.

Community
  • 1
  • 1
dejvuth
  • 6,986
  • 3
  • 33
  • 36