Java中的意外性能损失

Java中的意外性能损失,java,performance,collections,Java,Performance,Collections,我用Java实现了一个GapBuffer列表,我不明白为什么它会受到如此大的性能损失。用C#编写的类似代码的行为与预期的一样:插入到列表的中间比C#的列表实现要快得多。但是Java版本的表现很奇怪 以下是一些基准信息: Adding/removing 10,000,000 items @ the end of the dynamic array... ArrayList: 683 milliseconds GapBufferList: 416 milliseconds Adding/remov

我用Java实现了一个GapBuffer列表,我不明白为什么它会受到如此大的性能损失。用C#编写的类似代码的行为与预期的一样:插入到列表的中间比C#的列表实现要快得多。但是Java版本的表现很奇怪

以下是一些基准信息:

Adding/removing 10,000,000 items @ the end of the dynamic array...
ArrayList: 683 milliseconds
GapBufferList: 416 milliseconds

Adding/removing 100,000 items @ a random spot in the dynamic array...
  - ArrayList add: 721 milliseconds
  - ArrayList remove: 612 milliseconds
ArrayList: 1333 milliseconds
  - GapBufferList add: 1293 milliseconds
  - GapBufferList remove: 2775 milliseconds
GapBufferList: 4068 milliseconds

Adding/removing 100,000 items @ the beginning of the dynamic array...
ArrayList: 2422 milliseconds
GapBufferList: 13 milliseconds

Clearly, the GapBufferList is the better option.
如您所见,当您插入到列表的开头时,间隙缓冲区的行为与预期的一样:它比ArrayList好很多很多很多倍。然而,当在列表中的任意位置插入和删除时,间隙缓冲区有一个奇怪的性能损失,我无法解释。更奇怪的是,从GapBufferList中删除项的速度比向其中添加项的速度慢-根据我迄今为止运行的每个测试,删除项所需的时间大约是添加项所需时间的三倍,尽管它们的代码几乎相同:

@Override
public void add(int index, T t)
{
    if (index < 0 || index > back.length - gapSize) throw new IndexOutOfBoundsException();
    if (gapPos > index)
    {
        int diff = gapPos - index;
        for (int q = 1; q <= diff; q++)
            back[gapPos - q + gapSize] = back[gapPos - q];
    }
    else if (index > gapPos)
    {
        int diff = gapPos - index;
        for (int q = 0; q < diff; q++)
            back[gapPos + q] = back[gapPos + gapSize + q];
    }
    gapPos = index;
    if (gapSize == 0) increaseSize();
    back[gapPos++] = t; gapSize--;
}
@Override
public T remove(int index)
{
    if (index < 0 || index >= back.length - gapSize) throw new IndexOutOfBoundsException();
    if (gapPos > index + 1)
    {
        int diff = gapPos - (index + 1);
        for (int q = 1; q <= diff; q++)
            back[gapPos - q + gapSize] = back[gapPos - q];
    }
    else
    {
        int diff = (index + 1) - gapPos;
        for (int q = 0; q < diff; q++)
            back[gapPos + q] = back[gapPos + gapSize + q];
    }
    gapSize++;
    return back[gapPos = index];
}
@覆盖
公共无效添加(整数索引,T)
{
如果(index<0 | | index>back.length-gapSize)抛出新的IndexOutOfBoundsException();
如果(gapPos>索引)
{
int diff=gapPos-索引;
for(int q=1;q gapPos)
{
int diff=gapPos-索引;
对于(int q=0;q=back.length-gapSize)抛出新的IndexOutOfBoundsException();
如果(gapPos>索引+1)
{
int diff=gapPos-(索引+1);

对于(int q=1;q您可以手动复制数组的所有内容。请改用System.arraycopy。它比手动复制快得多(它是本机的,并使用特殊的魔法)。此外,您还可以查看ArrayList源代码,它肯定会使用System.arraycopy移动元素,而不是逐个移动

关于添加/删除方法的不同性能。用java编写微基准不是一件容易的任务。有关详细信息,请阅读,很难说在您的情况下会发生什么。但我看到,您首先填充列表,然后才从列表中删除项。在这种情况下,仅(索引>gapPos)分支被执行。因此,如果JIT编译了该代码,那么CPU分支预测可能会发生,这将进一步加速该代码(因为在您的测试用例中不太可能出现第一个分支).Remove会击中两个分支几乎相同的次数,并且不会发生任何优化。因此,很难说实际发生了什么。您应该尝试其他访问模式,例如。或者特别是带有间隙的crafter数组。或者其他示例。此外,您还应该从JVM输出一些调试信息,这可能会有所帮助

 public E remove(int index) {

     rangeCheck(index);

     modCount++;

     E oldValue = elementData(index);

     int numMoved = size - index - 1;

     if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index, numMoved);

     elementData[--size] = null; // Let gc do its work

     return oldValue;

 }

这就是ArrayList删除方法的来源。因为它使用的是
System.arraycopy
(非常巧妙)您正在使用循环,ArrayList分数。尝试实现类似的功能,您将看到类似的性能。

好的,谢谢,这部分解释了为什么它比ArrayList慢,但不是为什么
删除
函数比
添加
函数慢三倍。您知道这是为什么吗?同样的原因,ArrayList使用的是移除中的ame方法。好的,我接受这个作为答案,因为一旦我切换到System.arraycopy,在
remove
add
中奇怪的惩罚被移除。我仍然想知道为什么一开始会有这样的惩罚,但现在问题已经消失了。谢谢你的回答!arracopy方法是原生的,而且是guar与手动复制操作相比,Anted可以产生更好的结果…添加了一些关于不同添加/删除性能的见解。正如我所看到的,它们的平均复杂性相同。因此,差异可能是由一些微效应造成的,如JIT和CPU优化。