我對多線程增量的最佳性能做了調查。我檢查基於同步,AtomicInteger和自定義實現的實現,如AtomicInteger,但與parkNanos(1),失敗的CAS。Java增量基準
private int customAtomic() {
int ret;
for (;;) {
ret = intValue;
if (unsafe.compareAndSwapInt(this, offsetIntValue, ret, ++ret)) {
break;
}
LockSupport.parkNanos(1);
}
return ret;
}
我基於JMH製成基準:明確執行每個方法,每個方法具有消耗CPU(1,2,4,8,16倍)和只消耗CPU。在1-17線程上,每個基準測試方法在Intel(R)Xeon(R)CPU E5-1680 v2 @ 3.00GHz,8核心+ 8 HT 64Gb RAM上執行。 結果令我感到驚訝:
- CAS在1個線程中最有效。 2線程 - 與 監視器類似的結果。 3以上 - 比顯示器差,〜2倍。
- 在大多數情況下,自定義實現比監視器好2-3倍。
- 但是在自定義實現中,隨機有時會發生錯誤的執行。好的情況 - 50 op/microsec。,壞情況 - 0.5 op/microsec。
問題:
- 爲什麼的AtomicInteger不是基於同步,這是更有效率,那麼當前IMPL?
- 爲什麼AtomicInteger不使用LockSupport.parkNanos(1),在CAS上失敗?
- 爲什麼會在自定義實現中出現這種情況?
我試圖執行此測試幾次,秒殺總是發生在不同的線程數。另外我在另一臺機器上試過這個測試,結果是一樣的。也許這是測試中的問題。在StackProfiler定製IMPL的 「最壞情況」,我看到:
....[Thread state distributions]....................................................................
50.0% RUNNABLE
49.9% TIMED_WAITING
....[Thread state: RUNNABLE]........................................................................
43.3% 86.6% sun.misc.Unsafe.park
5.8% 11.6% com.jad.generated.IncrementBench_incrementCustomAtomicWithWork_jmhTest.incrementCustomAtomicWithWork_thrpt_jmhStub
0.8% 1.7% org.openjdk.jmh.infra.Blackhole.consumeCPU
0.1% 0.1% com.jad.IncrementBench$Worker.work
0.0% 0.0% java.lang.Thread.currentThread
0.0% 0.0% com.jad.generated.IncrementBench_incrementCustomAtomicWithWork_jmhTest._jmh_tryInit_f_benchmarkparams1_0
0.0% 0.0% org.openjdk.jmh.infra.generated.BenchmarkParams_jmhType_B1.<init>
....[Thread state: TIMED_WAITING]...................................................................
49.9% 100.0% sun.misc.Unsafe.park
在 「良好的情況下」:
....[Thread state distributions]....................................................................
88.2% TIMED_WAITING
11.8% RUNNABLE
....[Thread state: TIMED_WAITING]...................................................................
88.2% 100.0% sun.misc.Unsafe.park
....[Thread state: RUNNABLE]........................................................................
5.6% 47.9% sun.misc.Unsafe.park
3.1% 26.3% org.openjdk.jmh.infra.Blackhole.consumeCPU
2.4% 20.3% com.jad.generated.IncrementBench_incrementCustomAtomicWithWork_jmhTest.incrementCustomAtomicWithWork_thrpt_jmhStub
0.6% 5.5% com.jad.IncrementBench$Worker.work
0.0% 0.0% com.jad.generated.IncrementBench_incrementCustomAtomicWithWork_jmhTest.incrementCustomAtomicWithWork_Throughput
0.0% 0.0% java.lang.Thread.currentThread
0.0% 0.0% org.openjdk.jmh.infra.generated.BenchmarkParams_jmhType_B1.<init>
0.0% 0.0% sun.misc.Unsafe.putObject
0.0% 0.0% org.openjdk.jmh.runner.InfraControlL2.announceWarmdownReady
0.0% 0.0% sun.misc.Unsafe.compareAndSwapInt
Link to result graphs. X - threads count, Y - thpt, op/microsec
UPD
好的,我知道,我知道,當我使用parkNanos時,一個線程也可以長時間保持鎖(CAS)。帶CAS失敗的線程進入睡眠狀態,只有一個線程正在工作並增加值。我看到,對於大併發級別,當工作很小時 - AtomicInteger並不是更好的方法。但是,如果我們增加workSize,例如等級= CASThrpt/threadNum,它應該工作正常: 對於本地機器,我已經設置workSize = 300,我的測試結果:
Benchmark (workSize) Mode Cnt Score Error Units
IncrementBench.incrementAtomicWithWork 300 thrpt 3 4.133 ± 0.516 ops/us
IncrementBench.incrementCustomAtomicWithWork 300 thrpt 3 1.883 ± 0.234 ops/us
IncrementBench.lockIntWithWork 300 thrpt 3 3.831 ± 0.501 ops/us
IncrementBench.onlyWithWork 300 thrpt 3 4.339 ± 0.243 ops/us
的AtomicInteger - 取勝,鎖定 - 第二地方,自定義 - 第三。 但峯值的問題,仍不清楚。我忘了Java版本: Java(TM)SE運行時環境(內部版本1.7.0_79-b15) Java HotSpot™64位服務器虛擬機(版本24。79-b02,混合模式)
刪除對ParkNanos的呼叫。你想盡可能快地迭代。還要確保intValue是不穩定的。否則,ret = intValue可能看不到與CAS所做的相同的值 –
'但峯值問題仍然不清楚您是否檢查了日誌文件?有很多'<失敗:在JMH完成之前VM過早退出,顯式System.exit被調用?>'events – Ivan
如果您查看代碼,您將在@Setup方法中看到System.exit(0)。它用於刪除無意義的情況,例如:使用params workSize(2,4,8 ..)清除AtomicInteger增量。這種情況與參數無關。 –