簡體   English   中英

Java中的意外性能懲罰

[英]Unexpected Performance Penalty in Java

我在Java中實現了一個GapBuffer列表,我無法弄清楚為什么它會受到這樣的性能損失。 用C#編寫的類似代碼按預期運行:插入到列表中間的速度比C#的List實現要快得多。 但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];
}

將間隙緩沖區的代碼,可以發現這里 ,和基准,入口點代碼可以發現在這里 (您可以替換對Flow.***Exception任何引用Flow.***Exception RuntimeException Flow.***Exception 。)

您手動復制陣列的所有內容。 請改用System.arraycopy。 它比手動副本(它是原生的並使用特殊魔法)快得多。 您也可以查看ArrayList源代碼,它肯定會使用System.arraycopy而不是逐個移動元素。

關於添加/刪除方法的不同性能。 在java中編寫微基准測試並不是一件容易的事。 有關詳細信息,請參閱如何在Java中編寫正確的微基准測試? 很難說,你的情況究竟發生了什么。 但是我看到,您首先填充列表,然后才從中刪除項目。 在這種情況下,只執行(index> gapPos)分支。 因此,如果JIT編譯該代碼,則可能會發生CPU分支預測,這將進一步加速此代碼(因為在您的測試用例中不太可能出現第一個分支)。 刪除將幾乎相同次數擊中兩個分支,並且不會進行優化。 所以真的很難說,實際發生了什么。 例如,您應該嘗試其他訪問模式。 或特制的陣列,里面有間隙。 或者其他例子。 而且你應該從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的remove方法的來源。 因為它使用System.arraycopy (非常巧妙)並且您正在使用循環,ArrayList得分。 嘗試實現類似的東西,你會看到類似的性能。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM