4

Out of interest: Recently, I encountered a situation in one of my Java projects where I could store some data either in a two-dimensional array or make a dedicated class for it whose instances I would put into a one-dimensional array. So I wonder whether there exist some canonical design advice on this topic in terms of performance (runtime, memory consumption)?

Without regard of design patterns (extremely simplified situation), let's say I could store data like

class MyContainer {
  public double a;
  public double b;
  ...
}

and then

MyContainer[] myArray = new MyContainer[10000];
for(int i = myArray.length; (--i) >= 0;) {
  myArray[i] = new MyContainer();
}
...

versus

double[][] myData = new double[10000][2];  
...

I somehow think that the array-based approach should be more compact (memory) and faster (access). Then again, maybe it is not, arrays are objects too and array access needs to check indexes while object member access does not.(?) The allocation of the object array would probably(?) take longer, as I need to iteratively create the instances and my code would be bigger due to the additional class.

Thus, I wonder whether the designs of the common JVMs provide advantages for one approach over the other, in terms of access speed and memory consumption?

Many thanks.

Thomas Weise
  • 389
  • 6
  • 13
  • One clarification, Java arrays **are** `Object`(s). Even arrays of primitives. – Elliott Frisch Oct 08 '15 at 00:21
  • @ElliotFrisch: Yes, I am aware of that (see *...arrays are objects too...*). Still: It might be that the JVM/JIT treats arrays in a special way which could make them faster to access in one way or another. Also there could be caching/memory layout things, etc... Thus I also asked about *...designs of the common JVMs...*. – Thomas Weise Oct 08 '15 at 01:46
  • You might see if you can use an `enum`. Those are treated specially in that they are compile time constructs. – Elliott Frisch Oct 08 '15 at 01:52

3 Answers3

4

Then again, maybe it is not, arrays are objects too

That's right. So I think this approach will not buy you anything.

If you want to go down that route, you could flatten this out into a one-dimensional array (each of your "objects" then takes two slots). That would give you immediate access to all fields in all objects, without having to follow pointers, and the whole thing is just one big memory allocation: since your component type is primitive, there is just one object as far as memory allocation is concerned (the container array itself).

This is one of the motivations for people wanting to have structs and value types in Java, and similar considerations drive the development of specialized high-performance data structure libraries (that get rid of unneccessary object wrappers).

I would not worry about it, until you really have a huge datastructure, though. Only then will the overhead of the object-oriented way matter.

Community
  • 1
  • 1
Thilo
  • 257,207
  • 101
  • 511
  • 656
  • Indeed, that would be the most memory-friendly method. Speed-wise, would doing someting like `myarray[(i<<1)+j]` where, in our case, `i` would be the index and `j` would be either `0` or `1`, faster than doing `myarray[i][j]`? (I sort of think "yes", but again, I am not sure what kind of optimizations are done in common JVMs/JITs.) – Thomas Weise Oct 08 '15 at 01:49
  • 2
    Yes, that's what I meant by "having to follow pointers". Each of the component objects is a separate object in a different memory location. Flat array is much faster. – Thilo Oct 08 '15 at 04:04
3

I somehow think that the array-based approach should be more compact (memory) and faster (access)

It won't. You can easily confirm this by using Java Management interfaces:

com.sun.management.ThreadMXBean b = (com.sun.management.ThreadMXBean) ManagementFactory.getThreadMXBean();
long selfId = Thread.currentThread().getId();
long memoryBefore = b.getThreadAllocatedBytes(selfId);

// <-- Put measured code here

long memoryAfter = b.getThreadAllocatedBytes(selfId);
System.out.println(memoryAfter - memoryBefore);

Under measured code put new double[0] and new Object() and you will see that those allocations will require exactly the same amount of memory.

It might be that the JVM/JIT treats arrays in a special way which could make them faster to access in one way or another.

JIT do some vectorization of an array operations if for-loops. But it's more about speed of arithmetic operations rather than speed of access. Beside that, can't think about any.

Denis Bazhenov
  • 9,680
  • 8
  • 43
  • 65
  • Thanks for clarifying this. So we can expect that the total memory consumption of the objects and the 2d-array should be the same. Knowing this is good. One more thing I could think of in terms of "compactness": If I allocate, say `new double[1000][2]`, I'd expect that this should become one continuous chunk of memory on the heap. However, if I do `for(int i = 0; i<1000; i++) { data[i] = new MyObject(); }`, I am not sure, some of the objects may land on different places of the heap entirely(?). So this could be a penalty for the Object-array idea(?) – Thomas Weise Oct 08 '15 at 02:44
  • 1
    Regarding memory layout there should be no difference also. JVM allocates memory from a thread local buffer called TLAB (https://blogs.oracle.com/jonthecollector/entry/the_real_thing). Objects will be contiguous in memory regardless of their type as far as they fit in TLAB entirely. – Denis Bazhenov Oct 08 '15 at 02:52
3

The canonical advice that I've seen in this situation is that premature optimisation is the root of all evil. Following that means that you should stick with the code that is easiest to write / maintain / get past your code quality regime, and then look at optimisation if you have a measurable performance issue.

In your examples the memory consumption is similar because in the object case you have 10,000 references plus two doubles per reference, and in the 2D array case you have 10,000 references (the first dimension) to little arrays containing two doubles each. So both are one base reference plus 10,000 references plus 20,000 doubles.

A more efficient representation would be two arrays, where you'd have two base references plus 20,000 doubles.

double[] a = new double[10000];
double[] b = new double[10000];
John Bickers
  • 481
  • 2
  • 6
  • This is very easy-to-maintain, easy to understand, and it doesn't need much memory. However, it may introduce a performance problem, as it "rips" the data tuples/pairs apart. If I have `double[10000][2]`, then the 2 elements of each sub-array will be located right next to each other in memory, which is good for caches. In two single arrays (if I understand your suggestion correctly), then each element would land in a different array. The two elements of a tuple would be `10000` doubles away from each other in memory. This would probably lead to more cache misses and slow down processing speed. – Thomas Weise Oct 08 '15 at 03:19
  • @ThomasWeise, that is technically true, but the conclusion is wrong. In reality, you have multiple cache lines, and it doesn't matter in practice if you process a single continuous block of memory, or two continuous blocks of memory, which are apart from each other. The suggested approach is perfectly viable. However, if you really want to squeeze it, pack both arrays into a single, interleaving one. – Aivean Nov 03 '21 at 16:00