2017-02-17 25 views
2

我已經成立了一個JMH基準來衡量什麼是從一個空數組空,System.arraycopy更快Arrays.fill,zeroying一個DirectByteBuffer或zeroying一個unsafe的內存塊試圖回答這個question 讓我們拋開這zeroying直接分配的內存是一種罕見的情況,並討論我的基準測試結果。爲什麼直接內存「數組」比通常的Java數組更慢?

這裏的江鈴控股的基準片斷(full code available via a gist)包括unsafe.setMemory情況下,由@apangin在原崗位,byteBuffer.put(byte[], offset, length)longBuffer.put(long[], offset, length)的建議由@揚·舍費爾的建議:

@Benchmark 
@BenchmarkMode(Mode.SampleTime) 
@OutputTimeUnit(TimeUnit.NANOSECONDS) 
public void arrayFill() { 
    Arrays.fill(objectHolderForFill, null); 
} 

@Benchmark 
@BenchmarkMode(Mode.SampleTime) 
@OutputTimeUnit(TimeUnit.NANOSECONDS) 
public void arrayCopy() { 
    System.arraycopy(nullsArray, 0, objectHolderForArrayCopy, 0, objectHolderForArrayCopy.length); 
} 

@Benchmark 
@BenchmarkMode(Mode.SampleTime) 
@OutputTimeUnit(TimeUnit.NANOSECONDS) 
public void directByteBufferManualLoop() { 
    while (referenceHolderByteBuffer.hasRemaining()) { 
     referenceHolderByteBuffer.putLong(0); 
    } 
} 

@Benchmark 
@BenchmarkMode(Mode.SampleTime) 
@OutputTimeUnit(TimeUnit.NANOSECONDS) 
public void directByteBufferBatch() { 
    referenceHolderByteBuffer.put(nullBytes, 0, nullBytes.length); 
} 

@Benchmark 
@BenchmarkMode(Mode.SampleTime) 
@OutputTimeUnit(TimeUnit.NANOSECONDS) 
public void directLongBufferManualLoop() { 
    while (referenceHolderLongBuffer.hasRemaining()) { 
     referenceHolderLongBuffer.put(0L); 
    } 
} 

@Benchmark 
@BenchmarkMode(Mode.SampleTime) 
@OutputTimeUnit(TimeUnit.NANOSECONDS) 
public void directLongBufferBatch() { 
    referenceHolderLongBuffer.put(nullLongs, 0, nullLongs.length); 
} 


@Benchmark 
@BenchmarkMode(Mode.SampleTime) 
@OutputTimeUnit(TimeUnit.NANOSECONDS) 
public void unsafeArrayManualLoop() { 
    long addr = referenceHolderUnsafe; 
    long pos = 0; 
    for (int i = 0; i < size; i++) { 
     unsafe.putLong(addr + pos, 0L); 
     pos += 1 << 3; 
    } 
} 

@Benchmark 
@BenchmarkMode(Mode.SampleTime) 
@OutputTimeUnit(TimeUnit.NANOSECONDS) 
public void unsafeArraySetMemory() { 
    unsafe.setMemory(referenceHolderUnsafe, size*8, (byte) 0); 
} 

這裏是我(的Java 1.8 ,江鈴控股1.13,酷睿i3-6100U 2.30 GHz時,Win10):

100 elements 
Benchmark          Mode  Cnt Score Error Units 
ArrayNullFillBench.arrayCopy     sample 5234029 39,518 ± 0,991 ns/op 
ArrayNullFillBench.directByteBufferBatch  sample 6271334 43,646 ± 1,523 ns/op 
ArrayNullFillBench.directLongBufferBatch  sample 4615974 45,252 ± 2,352 ns/op 
ArrayNullFillBench.arrayFill     sample 4745406 76,997 ± 3,547 ns/op 
ArrayNullFillBench.unsafeArrayManualLoop  sample 5980381 78,811 ± 2,870 ns/op 
ArrayNullFillBench.unsafeArraySetMemory  sample 5985884 85,062 ± 2,096 ns/op 
ArrayNullFillBench.directLongBufferManualLoop sample 4697023 116,242 ± 2,579 ns/op WOW 
ArrayNullFillBench.directByteBufferManualLoop sample 7504629 208,440 ± 10,651 ns/op WOW 

I skipped all the loop implementations (except arrayFill for scale) from further tests 

1000 elements 
Benchmark         Mode  Cnt Score Error Units 
ArrayNullFillBench.arrayCopy    sample 6780681 184,516 ± 14,036 ns/op 
ArrayNullFillBench.directLongBufferBatch sample 4018778 293,325 ± 4,074 ns/op 
ArrayNullFillBench.directByteBufferBatch sample 4063969 313,171 ± 4,861 ns/op 
ArrayNullFillBench.arrayFill    sample 6862928 518,886 ± 6,372 ns/op 

10000 elements 
Benchmark         Mode  Cnt  Score Error Units 
ArrayNullFillBench.arrayCopy    sample 2551851 2024,543 ± 12,533 ns/op 
ArrayNullFillBench.directLongBufferBatch sample 2958517 4469,210 ± 10,376 ns/op 
ArrayNullFillBench.directByteBufferBatch sample 2892258 4526,945 ± 33,443 ns/op 
ArrayNullFillBench.arrayFill    sample 5689507 5028,592 ± 9,074 ns/op 

能否請您澄清以下幾個問題:

1. Why `unsafeArraySetMemory` is a bit but slower than `unsafeArrayManualLoop`? 
2. Why directByteBuffer is 2.5X-5X slower than others? 

回答

3

爲什麼unsafeArraySetMemory有點但比unsafeArrayManualLoop慢?

我的猜測是,它不能很好地設置多個長整數。它必須檢查你是否有東西,不是8的倍數。

爲什麼directByteBuffer比其他的要慢一個數量級?

一個數量級的數量級應該是10倍左右,大概是2.5倍左右。它必須檢查每個訪問並更新一個字段而不是局部變量。

注意:我發現JVM並不總是使用Unsafe循環展開代碼。你可以自己嘗試這樣做,看看它是否有幫助。

注意:本地代碼可以使用XMM 128位指令,並越來越多地使用這種方式,這就是複製速度如此之快的原因。訪問XMM指令可能會用Java 10.

+0

如果你不介意我已經糾正了我的帖子,以消除這個'數量級'混亂:-)感謝您指出,我已經標記爲asnwer有用。 – bashnesnos

1

這個比較有點不公平。在使用Array.fillSystem.arraycopy時,您正在使用單個操作,但您在DirectByteBuffer的情況下使用循環和putLong的多個調用。例如,如果你看看putLong的實現,你會看到有很多事情在那裏進行,比如檢查可訪問性。您應該嘗試使用批處理操作,如put(long[] src, int srcOffset, int longCount),看看會發生什麼。

+0

謝謝,我還會用批處理操作添加該案例。 'Array.fill'儘管在下面使用相同的循環。 'unsafe.setMemory'也是一種批處理操作。 – bashnesnos

+0

剛剛添加了你建議的情況(''ByteBuffer'和'LongBuffer'版本以防萬一)。似乎甚至使用'DirectBuffer'的批處理操作仍然比'System.arraycopy'慢,並且在更大的數組大小上趨向於更接近'Array.fill'。 – bashnesnos

相關問題