changeset 280:f8001b5a7f8e

Experimental @AuxCounters.
author shade
date Fri, 29 Nov 2013 16:09:28 +0400
parents 9575e99cfb05
children 4df884aa8a47
files jmh-core/src/main/java/org/openjdk/jmh/annotations/AuxCounters.java jmh-core/src/main/java/org/openjdk/jmh/processor/internal/GenerateMicroBenchmarkProcessor.java jmh-core/src/main/java/org/openjdk/jmh/processor/internal/StateObjectHandler.java jmh-samples/src/main/java/org/openjdk/jmh/samples/JMHSample_23_AuxCounters.java
diffstat 4 files changed, 183 insertions(+), 1 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jmh-core/src/main/java/org/openjdk/jmh/annotations/AuxCounters.java	Fri Nov 29 16:09:28 2013 +0400
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2005, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package org.openjdk.jmh.annotations;
+
+/**
+ * THIS IS AN EXPERIMENTAL API.
+ *  (That means it can be modified, deprecated and removed in future)
+ * <p>
+ * This annotation can be used to mark {@link State} objects as the bearers of
+ * auxiliary secondary results. Marking the class with this annotation will enable
+ * JMH to look for {int, long} fields, as well as methods returning {int, long}
+ * values, and treat their values as the operation counts in current iteration.
+ * <p>
+ * NOTE: You have to explicitly reset the state if you don't want the counters
+ * to be shared across the iterations.
+ * <p>
+ * NOTE: This functionality is not available for all {@link BenchmarkMode}-s.
+ */
+public @interface AuxCounters {
+}
--- a/jmh-core/src/main/java/org/openjdk/jmh/processor/internal/GenerateMicroBenchmarkProcessor.java	Fri Nov 29 15:42:22 2013 +0400
+++ b/jmh-core/src/main/java/org/openjdk/jmh/processor/internal/GenerateMicroBenchmarkProcessor.java	Fri Nov 29 16:09:28 2013 +0400
@@ -797,6 +797,9 @@
             if (!isSingleMethod) {
                 writer.println(ident(3) + "results.add(new ThroughputResult(ResultRole.SECONDARY, \"" + method.getSimpleName() + "\", res.getOperations(), res.getTime(), tu));");
             }
+            for (String ops : states.getAuxResultNames()) {
+                writer.println(ident(3) + "results.add(new ThroughputResult(ResultRole.SECONDARY, \"" + ops + "\", " + states.getAuxResultAccessor(ops) + ", res.getTime(), tu));");
+            }
             writer.println(ident(3) + "return results;");
             writer.println(ident(2) + "} else");
         }
@@ -888,7 +891,10 @@
             writer.println(ident(3) + "TimeUnit tu = (control.timeUnit != null) ? control.timeUnit : TimeUnit." + timeUnit + ";");
             writer.println(ident(3) + "results.add(new AverageTimeResult(ResultRole.PRIMARY, \"" + method.getSimpleName() + "\", res.getOperations(), res.getTime(), tu));");
             if (!isSingleMethod) {
-                writer.println(ident(3) + "results.add(new AverageTimeResult(ResultRole.SECONDARY, \"" + method.getSimpleName() + "\", res.getOperations(), res.getTime(), (control.timeUnit != null) ? control.timeUnit : TimeUnit." + timeUnit + "));");
+                writer.println(ident(3) + "results.add(new AverageTimeResult(ResultRole.SECONDARY, \"" + method.getSimpleName() + "\", res.getOperations(), res.getTime(), tu));");
+            }
+            for (String ops : states.getAuxResultNames()) {
+                writer.println(ident(3) + "results.add(new AverageTimeResult(ResultRole.SECONDARY, \"" + ops + "\", " + states.getAuxResultAccessor(ops) + ", res.getTime(), tu));");
             }
             writer.println(ident(3) + "return results;");
             writer.println(ident(2) + "} else");
--- a/jmh-core/src/main/java/org/openjdk/jmh/processor/internal/StateObjectHandler.java	Fri Nov 29 15:42:22 2013 +0400
+++ b/jmh-core/src/main/java/org/openjdk/jmh/processor/internal/StateObjectHandler.java	Fri Nov 29 16:09:28 2013 +0400
@@ -24,6 +24,7 @@
  */
 package org.openjdk.jmh.processor.internal;
 
+import org.openjdk.jmh.annotations.AuxCounters;
 import org.openjdk.jmh.annotations.Level;
 import org.openjdk.jmh.annotations.Scope;
 import org.openjdk.jmh.annotations.Setup;
@@ -36,6 +37,7 @@
 
 import javax.annotation.processing.ProcessingEnvironment;
 import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
 import javax.lang.model.element.ExecutableElement;
 import javax.lang.model.element.TypeElement;
 import javax.lang.model.util.ElementFilter;
@@ -66,6 +68,8 @@
     private int collapsedIndex = 0;
 
     private final HashMap<String, String> jmhTypes = new HashMap<String, String>();
+    private final Set<String> auxNames = new HashSet<String>();
+    private final Map<String, String> auxAccessors = new HashMap<String, String>();
 
     public StateObjectHandler(ProcessingEnvironment processingEnv) {
         this.processingEnv = processingEnv;
@@ -148,6 +152,33 @@
             args.put(execMethod.getSimpleName().toString(), so);
         }
 
+        // auxiliary result, produce the accessors
+        if (element.getAnnotation(AuxCounters.class) != null) {
+            for (Element sub : element.getEnclosedElements()) {
+                if (sub.getKind() == ElementKind.FIELD) {
+                    String fieldType = sub.asType().toString();
+                    if (fieldType.equals("int") || fieldType.equals("long")) {
+                        String name = sub.getSimpleName().toString();
+                        if (!auxNames.add(name)) {
+                            throw new GenerationException("Conflicting @" + AuxCounters.class.getSimpleName() + " fields", sub);
+                        }
+                        auxAccessors.put(name, so.localIdentifier + "." + name);
+                    }
+                }
+
+                if (sub.getKind() == ElementKind.METHOD) {
+                    String returnType = ((ExecutableElement) sub).getReturnType().toString();
+                    if (returnType.equals("int") || returnType.equals("long")) {
+                        String name = sub.getSimpleName().toString();
+                        if (!auxNames.add(name)) {
+                            throw new GenerationException("Conflicting @" + AuxCounters.class.getSimpleName() + " fields", sub);
+                        }
+                        auxAccessors.put(name, so.localIdentifier + "." + name + "()");
+                    }
+                }
+            }
+        }
+
         stateObjects.add(so);
 
         // walk the type hierarchy up to discover inherited helper methods
@@ -511,4 +542,13 @@
         }
         return s;
     }
+
+    public Collection<String> getAuxResultNames() {
+        return auxNames;
+    }
+
+    public String getAuxResultAccessor(String name) {
+        return auxAccessors.get(name);
+    }
+
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jmh-samples/src/main/java/org/openjdk/jmh/samples/JMHSample_23_AuxCounters.java	Fri Nov 29 16:09:28 2013 +0400
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2005, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package org.openjdk.jmh.samples;
+
+import org.openjdk.jmh.annotations.AuxCounters;
+import org.openjdk.jmh.annotations.Fork;
+import org.openjdk.jmh.annotations.GenerateMicroBenchmark;
+import org.openjdk.jmh.annotations.Level;
+import org.openjdk.jmh.annotations.Measurement;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+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 java.util.concurrent.TimeUnit;
+
+@OutputTimeUnit(TimeUnit.SECONDS)
+@Warmup(iterations = 5, time = 100, timeUnit = TimeUnit.MILLISECONDS)
+@Measurement(iterations = 5, time = 100, timeUnit = TimeUnit.MILLISECONDS)
+@Fork(1)
+public class JMHSample_23_AuxCounters {
+
+    /*
+     * In some weird cases you need to get the separate throughput/time
+     * metrics for the benchmarked code depending on the outcome of the
+     * current code. Trying to accommodate the cases like this, JMH optionally
+     * provides the special annotation which treats @State objects
+     * as the object bearing user counters. See @AuxCounters javadoc for
+     * the limitations.
+     */
+
+    @AuxCounters
+    @State(Scope.Thread)
+    public static class AdditionalCounters {
+        public int case1, case2;
+
+        @Setup(Level.Iteration)
+        public void clean() {
+            case1 = case2 = 0;
+        }
+
+        public int total() {
+            return case1 + case2;
+        }
+    }
+
+    /*
+     * This code measures the "throughput" in two parts of the branch.
+     * The @AuxCounters state above holds the counters which we increment
+     * ourselves, and then let JMH to use their values in the performance
+     * calculations. Note how we reset the counters on each iteration.
+     */
+
+    @GenerateMicroBenchmark
+    public void measure(AdditionalCounters counters) {
+        if (Math.random() < 0.1) {
+            counters.case1++;
+        } else {
+            counters.case2++;
+        }
+    }
+
+    /*
+     * HOW TO RUN THIS TEST:
+     *
+     * You can run this test with:
+     *    $ mvn clean install
+     *    $ java -jar target/microbenchmarks.jar ".*JMHSample_23.*"
+     */
+
+}