9

I am playing around with Java and would like to know just how different the following are in terms of performance. I understand that premature optimization is a plight on programming but my curiosity is just for future reference.

public class Type1{
     int[] data = new data[4];
     public int getData( int index ){
          return data[index];
     }
}

public class Type2{
     int data1;
     int data2;
     int data3;
     int data4;
     public int getData1(){
          return data1;
    }
    public int getData2(){
          return data2;
    }
    public int getData3(){
          return data3;
    }
    public int getData4(){
          return data4;
    }
}

It is understandable that there are many factors that might make a difference but there must be some noticeable performance in some way. Obviously class Type1 is a much more attractive solution in terms of design but it seems as if it holds an extra step when retrieving data, namely going into an array to get an int as oppose to Type2 which just goes straight for the data. Perhaps the differences are more noticeable if they held Class object arrays since it would probably be guaranteed that Java uses references for the array members. Am I completely off the ball?

4
  • I'm pretty sure that any difference is negligible. Commented Apr 25, 2014 at 11:20
  • Do you know if there is a way to decompile java code to compare its Assembly? Commented Apr 25, 2014 at 11:22
  • 5
    Of course the second will not do some extra steps of array (check index etc.) but you should remember that with the second style you will lose the benefits of arrays (and a less maintainable code!) Commented Apr 25, 2014 at 11:24
  • @sgtHale That should probably be a separate question, and it already exists on SO, but this link seemed to be useful: blog.leocad.io/how-to-decompile-dalvik-and-java-code Commented Apr 25, 2014 at 11:25

2 Answers 2

5

The runtime speed difference between the two approaches is surely insignificant in comparison to the impact of the difference in API usage.

However, if we re-arrange things so that the API is the same for both then we find that the runtime speed difference is indeed negligible. The following code times both approaches and I get ~13 seconds for both. Your results may differ.

It may be worth looking at the bytecode to see if the compiler has optimized away much of the difference though.

public class Program {
    public static interface Type {
        int getData1();

        int getData2();

        int getData3();

        int getData4();
    }

    public static class Type1 implements Type {
        private int[] data;

        public Type1(int data1, int data2, int data3, int data4) {
            data = new int[] { data1, data2, data3, data4 };
        }

        @Override
        public int getData1() {
            return data[0];
        }

        @Override
        public int getData2() {
            return data[1];
        }

        @Override
        public int getData3() {
            return data[2];
        }

        @Override
        public int getData4() {
            return data[3];
        }
    }

    public static class Type2 implements Type {
        private int data1;
        private int data2;
        private int data3;
        private int data4;

        public Type2(int data1, int data2, int data3, int data4) {
            this.data1 = data1;
            this.data2 = data2;
            this.data3 = data3;
            this.data4 = data4;
        }

        @Override
        public int getData1() {
            return data1;
        }

        @Override
        public int getData2() {
            return data2;
        }

        @Override
        public int getData3() {
            return data3;
        }

        @Override
        public int getData4() {
            return data4;
        }
    }

    public static void main(String[] args) {
        timeType(new Type1(1, 2, 3, 4));
        timeType(new Type2(1, 2, 3, 4));
    }

    private static void timeType(Type type) {
        long start = System.currentTimeMillis();
        int total = 0;

        for (long i = 0; i < 10000000000l; i++) {
            total += type.getData1();
            total += type.getData2();
            total += type.getData3();
            total += type.getData4();
        }

        System.out.println(total);
        System.out.println(System.currentTimeMillis() - start);
    }
}
Sign up to request clarification or add additional context in comments.

3 Comments

Well that surely is neglible. Thanks for the demonstration.
I think the access is pretty much the same, except the very little pointer arithmetic in the case of the array (sum of array pointer base + index). But there might exist an assembly instruction that does base+index anyway.
I'd recommend writing a JMH test to compare the performance of the two designs. Also the performance may vary if you deal with a high number of instances.
2

No, you are not. Your question is very similar to the problem in JGit implementation:

JGit struggles with not having an efficient way to represent a SHA-1. C can just say "unsigned char[20]" and have it inline into the container's memory allocation. A byte[20] in Java will cost an additional 16 bytes of memory, and be slower to access because the bytes themselves are in a different area of memory from the container object. We try to work around it by converting from a byte[20] to 5 ints, but that costs us machine instructions.

From http://marc.info/?l=git&m=124111702609723&w=2

See ObjectId implementation.

Also note you can provide the API of the Type1 for Type2 without (presumably expensive) index switch:

public class Type2 {
    private static final Unsafe U;
    private static final long data1Offset;
    static {
        try {
            Field f = Unsafe.class.getDeclaredField("theUnsafe");
            f.setAccessible(true);
            U = (Unsafe) f.get(null);
            Field data1Field = Type2.class.getDeclaredField("data1");
            data1Offset = U.objectFieldOffset(data1Field);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    int data1;
    int data2;
    int data3;
    int data4;
    public int getData(int index) {
         assert 0 <= index && index < 4 : "index out of range!";
         return U.getInt(this, data1Offset + (index << 2));
    }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.