AtomicsVsSynchronized sample

Ian Rogers irogers at google.com
Thu Jun 20 18:30:31 UTC 2019


Things look better on the jmh web site today but the change isn't
merged  - or at least I don't see it here:
http://hg.openjdk.java.net/code-tools/jmh/file/99d7b73cf1e3/jmh-samples/src/main/java/org/openjdk/jmh/samples
Is there anything blocking this getting merged?

Thanks,
Ian

On Wed, Jun 12, 2019 at 10:08 AM Ian Rogers <irogers at google.com> wrote:
>
> 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