AtomicsVsSynchronized sample
Ian Rogers
irogers at google.com
Wed Jun 12 17:08:45 UTC 2019
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