changeset 1172:fceec061059d

JMHSample_34_SafeLooping sample.
author shade
date Tue, 31 Mar 2015 17:33:03 +0300
parents a695e0c1c6b1
children adcd0af0a03b
files jmh-samples/src/main/java/org/openjdk/jmh/samples/JMHSample_34_SafeLooping.java
diffstat 1 files changed, 205 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jmh-samples/src/main/java/org/openjdk/jmh/samples/JMHSample_34_SafeLooping.java	Tue Mar 31 17:33:03 2015 +0300
@@ -0,0 +1,205 @@
+/*
+ * Copyright (c) 2014, Oracle America, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ *  * Neither the name of Oracle nor the names of its contributors may be used
+ *    to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.openjdk.jmh.samples;
+
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.CompilerControl;
+import org.openjdk.jmh.annotations.Fork;
+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.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.Warmup;
+import org.openjdk.jmh.infra.Blackhole;
+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.TimeUnit;
+
+@State(Scope.Thread)
+@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
+@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
+@Fork(3)
+@BenchmarkMode(Mode.AverageTime)
+@OutputTimeUnit(TimeUnit.NANOSECONDS)
+public class JMHSample_34_SafeLooping {
+
+    /*
+     * JMHSample_11_Loops warns about the dangers of using loops in @Benchmark methods.
+     * Sometimes, however, one needs to traverse through several elements in a dataset.
+     * This is hard to do without loops, and therefore we need to devise a scheme for
+     * safe looping.
+     */
+
+    /*
+     * Suppose we want to measure how much it takes to execute work() with different
+     * arguments. This mimics a frequent use case when multiple instances with the same
+     * implementation, but different data, is measured.
+     */
+
+    static final int BASE = 42;
+
+    static int work(int x) {
+        return BASE + x;
+    }
+
+    /*
+     * Every benchmark requires control. We do a trivial control for our benchmarks
+     * by checking the benchmark costs are growing linearly with increased task size.
+     * If it doesn't, then something wrong is happening.
+     */
+
+    @Param({"1", "10", "100", "1000"})
+    int size;
+
+    int[] xs;
+
+    @Setup
+    public void setup() {
+        xs = new int[size];
+        for (int c = 0; c < size; c++) {
+            xs[c] = c;
+        }
+    }
+
+    /*
+     * First, the obviously wrong way: "saving" the result into a local variable would not
+     * work. A sufficiently smart compiler will inline work(), and figure out only the last
+     * work() call needs to be evaluated. Indeed, if you run it with varying $size, the score
+     * will stay the same!
+     */
+
+    @Benchmark
+    public int measureWrong_1() {
+        int acc = 0;
+        for (int x : xs) {
+            acc = work(x);
+        }
+        return acc;
+    }
+
+    /*
+     * Second, another wrong way: "accumulating" the result into a local variable. While
+     * it would force the computation of each work() method, there are software pipelining
+     * effects in action, that can merge the operations between two otherwise distinct work()
+     * bodies. This will obliterate the benchmark setup.
+     *
+     * In this example, HotSpot does the unrolled loop, merges the $BASE operands into a single
+     * addition to $acc, and then does a bunch of very tight stores of $x-s. The final performance
+     * depends on how much of the loop unrolling happened *and* how much data is available to make
+     * the large strides.
+     */
+
+    @Benchmark
+    public int measureWrong_2() {
+        int acc = 0;
+        for (int x : xs) {
+            acc += work(x);
+        }
+        return acc;
+    }
+
+    /*
+     * Now, let's see how to measure these things properly. A very straight-forward way to
+     * break the merging is to sink each result to Blackhole. This will force runtime to compute
+     * every work() call in full. (We would normally like to care about several concurrent work()
+     * computations at once, but the memory effects from Blackhole.consume() prevent those optimization
+     * on most runtimes).
+     */
+
+    @Benchmark
+    public void measureRight_1(Blackhole bh) {
+        for (int x : xs) {
+            bh.consume(work(x));
+        }
+    }
+
+    /*
+     * DANGEROUS AREA, PLEASE READ THE DESCRIPTION BELOW.
+     *
+     * Sometimes, the cost of sinking the value into a Blackhole is dominating the nano-benchmark score.
+     * In these cases, one may try to do a make-shift "sinker" with non-inlineable method. This trick is
+     * *very* VM-specific, and can only be used if you are verifying the generated code (that's a good
+     * strategy when dealing with nano-benchmarks anyway).
+     *
+     * You SHOULD NOT use this trick in most cases. Apply only where needed.
+     */
+
+    @Benchmark
+    public void measureRight_2() {
+        for (int x : xs) {
+            sink(work(x));
+        }
+    }
+
+    @CompilerControl(CompilerControl.Mode.DONT_INLINE)
+    public static void sink(int v) {
+        // IT IS VERY IMPORTANT TO MATCH THE SIGNATURE TO AVOID AUTOBOXING.
+        // The method intentionally does nothing.
+    }
+
+
+    /*
+     * ============================== HOW TO RUN THIS TEST: ====================================
+     *
+     * You might notice measureWrong_1 does not depend on $size, measureWrong_2 has troubles with
+     * linearity, and otherwise much faster than both measureRight_*. You can also see measureRight_2
+     * is marginally faster than measureRight_1.
+     *
+     * You can run this test:
+     *
+     * a) Via the command line:
+     *    $ mvn clean install
+     *    $ java -jar target/benchmarks.jar JMHSample_34
+     *
+     * 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_34_SafeLooping.class.getSimpleName())
+                .warmupIterations(5)
+                .measurementIterations(5)
+                .forks(3)
+                .build();
+
+        new Runner(opt).run();
+    }
+
+}