AtomicsVsSynchronized sample

Dmitry Timofeiev dmitry.timofeiev at gmail.com
Sun Feb 25 15:38:03 UTC 2018


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