AtomicsVsSynchronized sample

Ian Rogers irogers at google.com
Mon Feb 26 17:06:31 UTC 2018


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