[英]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.