Java增量基准

时间:2015-11-01 01:43:59

标签: java increment atomic jmh

我对多线程增量的最佳性能进行调查。我检查了基于同步,AtomicInteger和自定义实现的实现,如AtomicInteger,但是使用parkNanos(1),在失败的CAS上。

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 Core + 8 HT 64Gb RAM上执行。 结果让我感到惊讶:

  1. CAS在1个帖子中最有效。 2个线程 - 与...类似的结果 监控。 3甚至更多 - 比监视器差,〜2次。
  2. 在大多数情况下,自定义实施的效果比监视器好2-3倍。
  3. 但是在自定义实现中,随机有时会发生执行不良。好的案例 - 50 op / microsec。,坏案例 - 0.5 op / microsec。
  4. 问题:

    1. 为什么AtomicInteger不是基于同步,它更有效率,然后是当前的impl?
    2. 为什么AtomicInteger不使用LockSupport.parkNanos(1),CAS失败?
    3. 为什么自定义实现会出现这种高峰?
    4. CustomIncrementGraph

      我尝试执行此测试几次,并且spike总是发生在不同的数字线程中。另外我在另一台机器上试过这个测试,结果是一样的。也许这是测试中的问题。在"坏情况"在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
      

      好的情况&#34;:

      ....[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,例如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 - 获胜,锁定 - 第二名,自定义 - 第三名。 但是尖峰问题仍然不明确。我忘记了java版本: Java(TM)SE运行时环境(版本1.7.0_79-b15) Java HotSpot(TM)64位服务器VM(内置24.79-b02,混合模式)

1 个答案:

答案 0 :(得分:1)

在同步的情况下,它往往是带锁的粘性,这意味着一个线程可以长时间保持锁定而不让另一个线程公平地抓住它。这对于多线程来说是非常糟糕的,但是如果你有一个基准测试,如果只有一个线程运行相对较长的时间,它会表现得更好。

您需要更改测试,以便在使用多个线程时比使用一个线程更好地运行,或者您实际上将测试哪个锁定策略具有最差的公平策略。

锁定策略试图调整锁定的执行方式,这就是为什么它可以改变行为,但它不能很好地完成,因为代码本来就不应该是多线程的。