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