problems, solutions, quirky behaviors

Main menu

ArrayList<int>

I’ve always thought that Java is in the wrong by not allowing generic collections of primitive types. I’ve experienced in the past some situations where boxing and unboxing was the reason of a bottleneck in a performance critical code and always felt when working with C# that for example Dictionary<int,int> is very useful and gives that extra edge with performance So today I’ve done some experiments:

I declared ArrayList<Integer> on which I performed 100 000 inserts followed by removing all elements one-by-one (always removing the first element):

6 ms. for inserts (includes amortized array resizing)

3790 ms. for removes

Oddly enough I found that the removals do not cause the shrinking of the array. Canonical implementations resize by a factor of two up whenever the space runs out, but also shrink the underlying array by a factor of two if it’s 3/4 empty
I checked the Java source code and indeed, there is no shrinking of the array with removals (also the increase is not by a factor of 2 but 3)

But, back to the original thought
Here’s the code i used for my resizable list of primitive integer values:

Java

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

privatestaticclassIntList{

int[]list=newint[64];

intsize=0;

publicvoidadd(inti){

ensureCapacity();

list[size++]=i;

}

publicintremove(intidx){

if(idx>size){

thrownewArrayIndexOutOfBoundsException(idx);

}

intmove=size-idx-1;

intel=list[idx];

if(move>0){

System.arraycopy(list,idx+1,list,idx,move);

}

size--;

returnel;

}

privatevoidensureCapacity(){

if(size==list.length){

list=Arrays.copyOf(list,list.length*3);

}

}

}

It uses the same System.arraycopy method to move elements around and Arrays.copyOf for growing as the ArrayList implementation

And the results?

3 ms. for inserts

3117 ms. for removes

Clearly the cost is dominated by remove and in particular by shifting all elements by one ‘left’ which each removal (no surprise there). The shrinking of the array doesn’t help since the move is made only for elements up to current size regardless of the actual array size (it actually adds a little)
So, to make the test more about arraylist of primitive types rather than about why I should never remove the first element from an arraylist and use some other structure instead, I modified the test to always remove the last element, thus making shifting of the elements unnecessary.I also increased the number of elements put to the list to 10 000 000 since it was running much faster now. The result startled me a little:

ArrayList add: 2096 ms.

ArrayList remove: 467 ms.

IntList add: 181 ms.

IntList remove: 85 ms.

Now that’s a bigger difference (especially for inserts). So I ran the profiler and here’s what I saw (I actually had to copy the ArrayList code to a separate class in order for the visualVM to show the stats):

ArrayList add: 461 ms.

IntList add: 296 ms.

ArrayList remove: 433 ms.

IntList remove: 189 ms.

Of course these numbers are ms calibrated to the system and are so-so accurate, but they show the scale. Without boxing taken into account the numbers are significantly different.I actually tested it again with a slightly modified version where the list of integers inserted was pre-generated and generation time was not added to insert time:

ArrayList add: 233 ms.

IntList add: 77 ms.

ArrayList remove: 205 ms.

IntList remove: 160 ms.

The differences here can be accounted for by the lack of error checking in the test application.