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