关于ArrayList性能(Java)的奇怪之处

时间:2013-10-17 01:13:08

标签: java performance arraylist

我对java.util.ArrayList#add()方法性能有些担忧(对我来说似乎太慢了),所以我下载了Java源代码并查看了ArrayList实现(看起来很好),我复制了clear()add()方法,并创建了自己的ArrayList2:

public class ArrayList2<E> 
{
    private static final int DEFAULT_CAPACITY = 10;

    private static final Object[] EMPTY_ELEMENTDATA = {};
    static int MAX_ARRAY_SIZE = 50000;

    private transient Object[] elementData;

    private int size;

    public ArrayList2(int initialCapacity) {
        super();
        if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
        this.elementData = new Object[initialCapacity];
    }

    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0)
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }    

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);
        elementData[size++] = e;
        return true;
    }

    private void ensureCapacityInternal(int minCapacity) {
        if (elementData == EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }

    private void ensureExplicitCapacity(int minCapacity) {

        if (minCapacity - elementData.length > 0){
            //System.out.println("WHAT");  //when this line is uncommented performance is improved
            grow(minCapacity);
        }
    }

    private void grow(int minCapacity) {
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

    public void clear() {

        for (int i = 0; i < size; i++)
            elementData[i] = null;

        size = 0;
    }
}

当然与java.util.ArrayList具有相同的性能。当第49行被取消注释时,会发生奇怪的事情。它包含这个:

System.out.println("What");

在此更改之后,add方法运行速度提高了3-4倍,这很奇怪,因为此行位于从未执行过的代码块中,因此不应对性能产生影响。我用一个简单的测试来衡量在不同方法中花费的时间。这是输出:

**************************************
Filling Array List 2 took : 2107
Emptying Array List 2 took : 149
**************************************
**************************************
Filling Array List 3 took : 565
Emptying Array List 3 took : 182
**************************************

这种逻辑上无关的变化如何能够如此显着地改善性能?有人可以解释一下吗?它对我来说真的没有任何意义,而且在我看来这是java.util.ArrayList中的一个重大性能问题。

您可以在此处下载源代码:

http://goo.gl/62Ri5T

你可以使用这个maven命令运行它:

mvn exec:java -Dexec.mainClass="Test" 

2 个答案:

答案 0 :(得分:4)

正如评论中提到的那样,这似乎是一个不合适的微观基准。

您会看到,即使if语句的内部部分可能未在您的特定情况下执行,也会经常调用方法本身。现在,由于对System.out的内部调用,方法字节代码更长,因此JIT可能在优化该方法时不那么诱惑(“短”方法很快被内联;对System.out的调用可能会使它不那么有趣比其他地方的其他方法更优化。)

因此,如果第一个解决方案先前得到优化,为什么测试中的速度会变慢?因为JIT编译是异步执行的,并且消耗时间。因此,在进行优化的同时,方法会变慢,直到完全优化。然后变得非常快。只需使用参数“-XX:-PrintCompilation”执行Java即可。

但无论如何......使用卡尺。真。

答案 1 :(得分:2)

现在我确定......它的基准是糟糕的。试着重复10次。第一个结果:

**************************************
Filling Array List 2 took  : 1754
Emptying Array List 2 took : 191
**************************************
**************************************
Filling Array List 3 took  : 534
Emptying Array List 3 took : 188
**************************************

最后的结果,没有将定时器归零,即显示累计时间:

**************************************
Filling Array List 2 took  : 18647
Emptying Array List 2 took : 1945
**************************************
**************************************
Filling Array List 3 took  : 17310
Emptying Array List 3 took : 1865
**************************************

将计时器归零的最后结果,即显示上次运行的时间:

**************************************
Filling Array List 2 took  : 1733
Emptying Array List 2 took : 179
**************************************
**************************************
Filling Array List 3 took  : 1897
Emptying Array List 3 took : 184
**************************************

你可以看到数字毫无意义,对吗?只需切换到caliper