2015-11-01 44 views
7

我對多線程增量的最佳性能做了調查。我檢查基於同步,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上執行。 結果令我感到驚訝:

  1. CAS在1個線程中最有效。 2線程 - 與 監視器類似的結果。 3以上 - 比顯示器差,〜2倍。
  2. 在大多數情況下,自定義實現比監視器好2-3倍。
  3. 但是在自定義實現中,隨機有時會發生錯誤的執行。好的情況 - 50 op/microsec。,壞情況 - 0.5 op/microsec。

問題:

  1. 爲什麼的AtomicInteger不是基於同步,這是更有效率,那麼當前IMPL?
  2. 爲什麼AtomicInteger不使用LockSupport.parkNanos(1),在CAS上失敗?
  3. 爲什麼會在自定義實現中出現這種情況?

CustomIncrementGraph

我試圖執行此測試幾次,秒殺總是發生在不同的線程數。另外我在另一臺機器上試過這個測試,結果是一樣的。也許這是測試中的問題。在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 benchmark code

Link to result graphs. X - threads count, Y - thpt, op/microsec

Link to RAW log

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,混合模式)

+1

刪除對ParkNanos的呼叫。你想盡可能快地迭代。還要確保intValue是不穩定的。否則,ret = intValue可能看不到與CAS所做的相同的值 –

+0

'但峯值問題仍然不清楚您是否檢查了日誌文件?有很多'<失敗:在JMH完成之前VM過早退出,顯式System.exit被調用?>'events – Ivan

+0

如果您查看代碼,您將在@Setup方法中看到System.exit(0)。它用於刪除無意義的情況,例如:使用params workSize(2,4,8 ..)清除AtomicInteger增量。這種情況與參數無關。 –

回答

1

在同步的情況下,它往往是粘滯的鎖,這意味着一個線程可以長時間保持鎖而不會讓另一個線程公平地抓住它。這對於多線程來說是非常糟糕的,但是如果你有一個基準,如果只有一個線程在相對較長的時間內運行,性能會更好,那麼這是非常好的。

您需要更改測試,以便在使用多個線程時比使用一個線程時運行得更好,否則實際上會測試哪個鎖定策略具有最差的公平策略。

鎖定策略嘗試調整鎖定的執行方式,這就是爲什麼它可以改變行爲,但它不能很好地完成工作,因爲代碼本來不應該是多線程的。