AtomicsVsSynchronized sample

Ian Rogers irogers at google.com
Wed Jun 12 17:08:45 UTC 2019


Did this get merged? I tried to look on:
http://hg.openjdk.java.net/code-tools/jmh/
but the web site is giving a python error.

Thanks!
Ian

On Mon, Feb 26, 2018 at 9:06 AM Ian Rogers <irogers at google.com> wrote:
>
> Thanks, done.
>
>
> On Sun, Feb 25, 2018 at 7:38 AM Dmitry Timofeiev <dmitry.timofeiev at gmail.com> wrote:
>>
>> Hi Ian,
>>
>> That's interesting, thank you! I've left a couple of suggestions inline:
>>
>>
>> On 24.02.18 00:32, Ian Rogers wrote:
>>
>> Hi,
>>
>> I'd like to contribute the attached, hopefully fun, sample.
>>
>> Thanks,
>> Ian
>>
>>
>> jmh-atomic-vs-synchronized.patch
>>
>> diff -r 25d8b2695bac jmh-samples/pom.xml
>> --- a/jmh-samples/pom.xml Tue Jan 23 09:44:15 2018 +0100
>> +++ b/jmh-samples/pom.xml Thu Feb 22 13:38:32 2018 -0800
>> @@ -74,9 +74,9 @@
>>                  <groupId>org.apache.maven.plugins</groupId>
>>                  <artifactId>maven-compiler-plugin</artifactId>
>>                  <configuration>
>> -                    <compilerVersion>1.7</compilerVersion>
>> -                    <source>1.7</source>
>> -                    <target>1.7</target>
>> +                    <compilerVersion>1.8</compilerVersion>
>> +                    <source>1.8</source>
>> +                    <target>1.8</target>
>>                  </configuration>
>>              </plugin>
>>              <plugin>
>> diff -r 25d8b2695bac jmh-samples/src/main/java/org/openjdk/jmh/samples/JMHSample_39_AtomicsVsSynchronized.java
>> --- /dev/null Thu Jan 01 00:00:00 1970 +0000
>> +++ b/jmh-samples/src/main/java/org/openjdk/jmh/samples/JMHSample_39_AtomicsVsSynchronized.java Thu Feb 22 13:38:32 2018 -0800
>> @@ -0,0 +1,207 @@
>> +package org.openjdk.jmh.samples;
>> +
>> +import org.openjdk.jmh.annotations.Benchmark;
>> +import org.openjdk.jmh.annotations.BenchmarkMode;
>> +import org.openjdk.jmh.annotations.Fork;
>> +import org.openjdk.jmh.annotations.Group;
>> +import org.openjdk.jmh.annotations.Measurement;
>> +import org.openjdk.jmh.annotations.Mode;
>> +import org.openjdk.jmh.annotations.OutputTimeUnit;
>> +import org.openjdk.jmh.annotations.Param;
>> +import org.openjdk.jmh.annotations.Scope;
>> +import org.openjdk.jmh.annotations.State;
>> +import org.openjdk.jmh.annotations.Threads;
>> +import org.openjdk.jmh.annotations.Warmup;
>> +import org.openjdk.jmh.runner.Runner;
>> +import org.openjdk.jmh.runner.RunnerException;
>> +import org.openjdk.jmh.runner.options.Options;
>> +import org.openjdk.jmh.runner.options.OptionsBuilder;
>> +
>> +import java.util.concurrent.atomic.AtomicLong;
>> +import java.util.concurrent.atomic.AtomicLongFieldUpdater;
>> +import java.util.concurrent.atomic.LongAccumulator;
>> +import java.util.concurrent.TimeUnit;
>> +
>> + at BenchmarkMode(Mode.AverageTime)
>> + at OutputTimeUnit(TimeUnit.MILLISECONDS)
>> + at Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
>> + at Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
>> + at Threads(Threads.MAX)
>> + at Fork(5)
>> +public class JMHSample_39_AtomicsVsSynchronized {
>> +
>> +    /*
>> +     * Atomics provide a way to avoid a lock that can reduce
>> +     * contention, but the JVM does runtime lock profiling and
>> +     * compiler optimizations to elide and coarsen locks. Therefore,
>> +     * atomics may be a source of premature performance
>> +     * optimization.
>> +     *
>> +     * This benchmark hopes to demonstrate that synchronization
>>
>> I'd say stronger (because if it does not demonstrate, it's doesn't serve its purpose), e.g.:
>> This benchmark demonstrates that sometimes (= probably not always)  synchronization is the fastest option.
>>
>> +     * remains the performant option. The benchmark consists of an
>>
>> It consists of …
>>
>> +     * implementation of Random based on java.util.Random, and a
>> +     * benchmark loop that computes pi using the monte carlo method of
>> +     * seeing whether random values within a square are within a
>> +     * circle. Access to the seed within the Random implementation is
>> +     * done via atomics or synchronization.
>> +     */
>> +
>> +    /**
>> +     * Stripped down Random implementation providing nextDouble via an
>> +     * overridden next implementation.
>> +     */
>> +    static abstract class Random {
>> +      private static final double DOUBLE_UNIT = 0x1.0p-53; // 1.0 / (1L << 53)
>> +      protected static final long multiplier = 0x5DEECE66DL;
>> +      protected static final long addend = 0xBL;
>> +      protected static final long mask = (1L << 48) - 1;
>> +
>> +      final double nextDouble() {
>> +        return (((long) (next(26)) << 27) + next(27)) * DOUBLE_UNIT;
>> +      }
>> +
>> +      protected abstract int next(int bits);
>> +    }
>> +
>> +    /**
>> +     * Workload for different Random implementations.
>> +     */
>> +    private double commonComputePi(Random r) {
>> +        // Number of repetitions of benchmark loop a larger number gives
>>
>> // Number of repetitions of benchmark loop <some punctuation is missing> a larger…
>>
>> +        // a better approximation of PI.
>> +        final int reps = 1000000;
>> +        int under_curve = 0;
>> +        for (int i = 0; i < reps; i++) {
>> +            double x = r.nextDouble();
>> +            double y = r.nextDouble();
>> +            if (x * x + y * y <= 1.0) {
>> +                under_curve++;
>> +            }
>> +        }
>> +        double pi = ((double) under_curve / reps) * 4.0;
>> +        return pi;
>> +    }
>> +
>> +    /*
>> +     * BASELINE EXPERIMENT: Using synchronized.
>> +     */
>> +
>> +    @State(Scope.Group)
>> +    public static class SynchronizedRandom extends Random {
>> +        private long seed = System.nanoTime();
>> +
>> +        @Override
>> +        protected synchronized int next(int bits) {
>> +            seed = (seed * multiplier + addend) & mask;
>> +            return (int) (seed >>> (48 - bits));
>> +        }
>> +    }
>> +
>> +    @Benchmark
>> +    @Group("synchronized")
>> +    public double computePi(SynchronizedRandom s) {
>> +        return commonComputePi(s);
>> +    }
>> +
>> +    /*
>> +     * APPROACH 1: Vanilla atomic.
>> +     *
>> +     * Avoid lock contention on seed by an atomic CAS.
>> +     */
>> +
>> +    @State(Scope.Group)
>> +    public static class AtomicRandom extends Random {
>> +        private final AtomicLong seed = new AtomicLong(System.nanoTime());
>> +
>>
>> Here and below -- missing @Override.
>>
>> +        protected int next(int bits) {
>> +            long oldseed, nextseed;
>> +            AtomicLong seed = this.seed;
>> +            do {
>> +                oldseed = seed.get();
>> +                nextseed = (oldseed * multiplier + addend) & mask;
>> +            } while (!seed.compareAndSet(oldseed, nextseed));
>> +            return (int) (nextseed >>> (48 - bits));
>> +        }
>> +    };
>> +
>> +    @Benchmark
>> +    @Group("atomic")
>> +    public double computePi(AtomicRandom s) {
>> +        return commonComputePi(s);
>> +    }
>> +
>> +    /*
>> +     * APPROACH 2: Use an atomic field updater.
>> +     *
>> +     * Avoid the indirection overhead of approach 1 with a field updater.
>> +     */
>> +    @State(Scope.Group)
>> +    public static class UpdaterRandom extends Random {
>> +        private static final AtomicLongFieldUpdater<UpdaterRandom> updater =
>> +                AtomicLongFieldUpdater.newUpdater(UpdaterRandom.class, "seed");
>> +        private volatile long seed = System.nanoTime();
>> +
>> +        protected int next(int bits) {
>> +            long oldseed, nextseed;
>> +            do {
>> +                oldseed = updater.get(this);
>> +                nextseed = (oldseed * multiplier + addend) & mask;
>> +            } while (!updater.compareAndSet(this, oldseed, nextseed));
>> +            return (int) (nextseed >>> (48 - bits));
>> +        }
>> +    }
>> +
>> +    @Benchmark
>> +    @Group("updater")
>> +    public double computePi(UpdaterRandom s) {
>> +        return commonComputePi(s);
>> +    }
>> +
>> +    /*
>> +     * APPROACH 3: Long accumulator.
>> +     *
>> +     * A variant of approach 1 with the Java 8 LongAccumulator.
>> +     */
>> +    @State(Scope.Group)
>> +    public static class AccumulatorRandom extends Random {
>> +        private final LongAccumulator seed = new LongAccumulator(
>> +            (oldseed, ignored) -> (oldseed * multiplier + addend) & mask,
>> +            System.nanoTime());
>> +
>> +        protected int next(int bits) {
>> +            seed.accumulate(0);
>> +            return (int) (seed.get() >>> (48 - bits));
>> +        }
>> +    }
>> +
>> +    @Benchmark
>> +    @Group("accumulator")
>> +    public double computePi(AccumulatorRandom s) {
>> +        return commonComputePi(s);
>> +    }
>> +
>> +    /*
>> +     * ============================== HOW TO RUN THIS TEST: ====================================
>> +     *
>> +     * Note the slowdowns.
>> +     *
>> +     * You can run this test:
>> +     *
>> +     * a) Via the command line:
>> +     *    $ mvn clean install
>> +     *    $ java -jar target/benchmarks.jar JMHSample_39
>> +     *
>> +     * b) Via the Java API:
>> +     *    (see the JMH homepage for possible caveats when running from IDE:
>> +     *      http://openjdk.java.net/projects/code-tools/jmh/)
>> +     */
>> +
>> +    public static void main(String[] args) throws RunnerException {
>> +        Options opt = new OptionsBuilder()
>> +                .include(JMHSample_39_AtomicsVsSynchronized.class.getSimpleName())
>> +                .build();
>> +
>> +        new Runner(opt).run();
>> +    }
>> +
>> +}
>>
>> Cheers,
>> Dmitry
>>


More information about the jmh-dev mailing list