我正在研究最佳的多线程增量性能。我检查了基于同步、AtomicInteger和自定义实现(类似于AtomicInteger,但在失败的CAS时使用parkNanos(1))。
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。每个基准测试方法在Intel(R)Xeon(R)CPU E5-1680 v2 @ 3.00GHz、8核+8 HT 64GB RAM上运行,在1-17个线程上执行。结果让我吃惊:
- CAS在1个线程中最有效。2个线程时,与监视器的结果相似。3个及以上线程时差于监视器,约为2倍。
- 在大多数情况下,自定义实现比监视器快2-3倍。
- 但是,在自定义实现中,有时会随机发生糟糕的执行。好的情况下-50 op/microsec。,坏的情况下-0.5 op/microsec.
问题:
- 为什么AtomicInteger不基于同步,它比当前实现更高效?
- 为什么AtomicInteger 在CAS失败时不使用LockSupport.parkNanos(1)?
- 为什么自定义实现会出现这些峰值?
我尝试执行此测试了几次,峰值总是发生在不同数量的线程中。我还在其他机器上尝试过这个测试,结果是一样的。也许测试存在问题。在StackProfiler中,在自定义实现的“坏情况”下,我看到:
....[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
更新
好的,我知道,当我使用parkNanos时,一个线程也可能长时间持有锁(CAS)。CAS失败的线程会进入睡眠状态,只有一个线程在工作并递增值。我看到,在大并发级别下,当工作非常小的时候- AtomicInteger不是更好的方法。但是如果我们增加workSize,例如将level = 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 - 第一;Win, Lock - 第二;自定义 - 第三。 但是还存在尖峰问题,尚不清楚。我忘记提及Java版本: Java(TM) SE Runtime Environment (build 1.7.0_79-b15) Java HotSpot(TM) 64-Bit Server VM (build 24.79-b02, mixed mode)
<failure: VM prematurely exited before JMH had finished with it, explicit System.exit was called?>
事件。 - Ivan MamontovincrementCustomAtomicWithWork
失败了,原因是failure: VM prematurely exited
,结果为0.301 ops/us。这样可以吗? - Ivan MamontovincrementCustomAtomicWithWork
在你的日志中失败了? - Ivan Mamontov