我对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中的一个重大性能问题。
您可以在此处下载源代码:
你可以使用这个maven命令运行它:
mvn exec:java -Dexec.mainClass="Test"
答案 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。