changeset 248:6f7fe0125263

Make asymmetric @Groups dynamic, thread distribution selectable at runtime.
author shade
date Tue, 19 Nov 2013 21:11:26 +0400
parents 58a3fa5dae6d
children 8d1393224178
files jmh-core-it/src/test/java/org/openjdk/jmh/it/asymm/ExactThreadCountTest.java jmh-core-it/src/test/java/org/openjdk/jmh/it/asymm/Zero1ThreadCountTest.java jmh-core-it/src/test/java/org/openjdk/jmh/it/asymm/Zero2ThreadCountTest.java jmh-core-it/src/test/java/org/openjdk/jmh/it/sharing/GroupBenchSharingTest.java jmh-core-it/src/test/java/org/openjdk/jmh/it/sharing/GroupDefaultBenchSharingTest.java jmh-core-it/src/test/java/org/openjdk/jmh/it/sharing/GroupDefaultStateSharingTest.java jmh-core-it/src/test/java/org/openjdk/jmh/it/sharing/GroupStateSharingTest.java jmh-core-it/src/test/java/org/openjdk/jmh/it/times/GroupBenchHelperTimesTest.java jmh-core-it/src/test/java/org/openjdk/jmh/it/times/GroupStateHelperTimesTest.java jmh-core/src/main/java/org/openjdk/jmh/annotations/GroupThreads.java jmh-core/src/main/java/org/openjdk/jmh/logic/ThreadControl.java jmh-core/src/main/java/org/openjdk/jmh/processor/internal/GenerateMicroBenchmarkProcessor.java jmh-core/src/main/java/org/openjdk/jmh/processor/internal/GroupValidationProcessor.java jmh-core/src/main/java/org/openjdk/jmh/processor/internal/MethodGroup.java jmh-core/src/main/java/org/openjdk/jmh/processor/internal/MethodInvocation.java jmh-core/src/main/java/org/openjdk/jmh/processor/internal/StateObjectHandler.java jmh-core/src/main/java/org/openjdk/jmh/runner/BenchmarkRecord.java jmh-core/src/main/java/org/openjdk/jmh/runner/LoopMicroBenchmarkHandler.java jmh-core/src/main/java/org/openjdk/jmh/runner/MicroBenchmarkHandlers.java jmh-core/src/main/java/org/openjdk/jmh/runner/options/ChainedOptionsBuilder.java jmh-core/src/main/java/org/openjdk/jmh/runner/options/CommandLineOptions.java jmh-core/src/main/java/org/openjdk/jmh/runner/options/Options.java jmh-core/src/main/java/org/openjdk/jmh/runner/options/OptionsBuilder.java jmh-core/src/main/java/org/openjdk/jmh/runner/parameters/BenchmarkParams.java jmh-core/src/main/java/org/openjdk/jmh/runner/parameters/IterationParams.java jmh-core/src/main/java/org/openjdk/jmh/util/Utils.java jmh-core/src/main/resources/META-INF/services/javax.annotation.processing.Processor jmh-core/src/test/java/org/openjdk/jmh/logic/results/TestAggregateResult.java jmh-core/src/test/resources/org/openjdk/jmh/runner/MicroBenchmarks jmh-samples/src/main/java/org/openjdk/jmh/samples/JMHSample_15_Asymmetric.java
diffstat 30 files changed, 516 insertions(+), 172 deletions(-) [+]
line wrap: on
line diff
--- a/jmh-core-it/src/test/java/org/openjdk/jmh/it/asymm/ExactThreadCountTest.java	Tue Nov 19 15:07:36 2013 +0400
+++ b/jmh-core-it/src/test/java/org/openjdk/jmh/it/asymm/ExactThreadCountTest.java	Tue Nov 19 21:11:26 2013 +0400
@@ -31,12 +31,14 @@
 import org.openjdk.jmh.annotations.Fork;
 import org.openjdk.jmh.annotations.GenerateMicroBenchmark;
 import org.openjdk.jmh.annotations.Group;
+import org.openjdk.jmh.annotations.GroupThreads;
+import org.openjdk.jmh.annotations.Level;
 import org.openjdk.jmh.annotations.Measurement;
 import org.openjdk.jmh.annotations.Mode;
 import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
 import org.openjdk.jmh.annotations.State;
 import org.openjdk.jmh.annotations.TearDown;
-import org.openjdk.jmh.annotations.Threads;
 import org.openjdk.jmh.annotations.Warmup;
 import org.openjdk.jmh.it.Fixtures;
 import org.openjdk.jmh.runner.Runner;
@@ -59,7 +61,13 @@
     private Set<Thread> test1threads = Collections.synchronizedSet(new HashSet<Thread>());
     private Set<Thread> test2threads = Collections.synchronizedSet(new HashSet<Thread>());
 
-    @TearDown
+    @Setup(Level.Iteration)
+    public void setup() {
+        test1threads.clear();
+        test2threads.clear();
+    }
+
+    @TearDown(Level.Iteration)
     public void check() {
         Assert.assertEquals(1, test1threads.size());
         Assert.assertEquals(2, test2threads.size());
@@ -67,7 +75,7 @@
 
     @GenerateMicroBenchmark
     @Group("test")
-    @Threads(1)
+    @GroupThreads(1)
     public void test1() {
         test1threads.add(Thread.currentThread());
         Fixtures.work();
@@ -75,7 +83,7 @@
 
     @GenerateMicroBenchmark
     @Group("test")
-    @Threads(2)
+    @GroupThreads(2)
     public void test2() {
         test2threads.add(Thread.currentThread());
         Fixtures.work();
--- a/jmh-core-it/src/test/java/org/openjdk/jmh/it/asymm/Zero1ThreadCountTest.java	Tue Nov 19 15:07:36 2013 +0400
+++ b/jmh-core-it/src/test/java/org/openjdk/jmh/it/asymm/Zero1ThreadCountTest.java	Tue Nov 19 21:11:26 2013 +0400
@@ -31,12 +31,12 @@
 import org.openjdk.jmh.annotations.Fork;
 import org.openjdk.jmh.annotations.GenerateMicroBenchmark;
 import org.openjdk.jmh.annotations.Group;
+import org.openjdk.jmh.annotations.GroupThreads;
 import org.openjdk.jmh.annotations.Measurement;
 import org.openjdk.jmh.annotations.Mode;
 import org.openjdk.jmh.annotations.Scope;
 import org.openjdk.jmh.annotations.State;
 import org.openjdk.jmh.annotations.TearDown;
-import org.openjdk.jmh.annotations.Threads;
 import org.openjdk.jmh.annotations.Warmup;
 import org.openjdk.jmh.it.Fixtures;
 import org.openjdk.jmh.runner.Runner;
@@ -67,7 +67,7 @@
 
     @GenerateMicroBenchmark
     @Group("test")
-    @Threads(0)
+    @GroupThreads(0)
     public void test1() {
         test1threads.add(Thread.currentThread());
         Fixtures.work();
@@ -75,7 +75,7 @@
 
     @GenerateMicroBenchmark
     @Group("test")
-    @Threads(2)
+    @GroupThreads(2)
     public void test2() {
         test2threads.add(Thread.currentThread());
         Fixtures.work();
--- a/jmh-core-it/src/test/java/org/openjdk/jmh/it/asymm/Zero2ThreadCountTest.java	Tue Nov 19 15:07:36 2013 +0400
+++ b/jmh-core-it/src/test/java/org/openjdk/jmh/it/asymm/Zero2ThreadCountTest.java	Tue Nov 19 21:11:26 2013 +0400
@@ -31,12 +31,12 @@
 import org.openjdk.jmh.annotations.Fork;
 import org.openjdk.jmh.annotations.GenerateMicroBenchmark;
 import org.openjdk.jmh.annotations.Group;
+import org.openjdk.jmh.annotations.GroupThreads;
 import org.openjdk.jmh.annotations.Measurement;
 import org.openjdk.jmh.annotations.Mode;
 import org.openjdk.jmh.annotations.Scope;
 import org.openjdk.jmh.annotations.State;
 import org.openjdk.jmh.annotations.TearDown;
-import org.openjdk.jmh.annotations.Threads;
 import org.openjdk.jmh.annotations.Warmup;
 import org.openjdk.jmh.it.Fixtures;
 import org.openjdk.jmh.runner.Runner;
@@ -67,7 +67,7 @@
 
     @GenerateMicroBenchmark
     @Group("test")
-    @Threads(1)
+    @GroupThreads(1)
     public void test1() {
         test1threads.add(Thread.currentThread());
         Fixtures.work();
@@ -75,7 +75,7 @@
 
     @GenerateMicroBenchmark
     @Group("test")
-    @Threads(0)
+    @GroupThreads(0)
     public void test2() {
         test2threads.add(Thread.currentThread());
         Fixtures.work();
--- a/jmh-core-it/src/test/java/org/openjdk/jmh/it/sharing/GroupBenchSharingTest.java	Tue Nov 19 15:07:36 2013 +0400
+++ b/jmh-core-it/src/test/java/org/openjdk/jmh/it/sharing/GroupBenchSharingTest.java	Tue Nov 19 21:11:26 2013 +0400
@@ -31,13 +31,13 @@
 import org.openjdk.jmh.annotations.Fork;
 import org.openjdk.jmh.annotations.GenerateMicroBenchmark;
 import org.openjdk.jmh.annotations.Group;
+import org.openjdk.jmh.annotations.GroupThreads;
 import org.openjdk.jmh.annotations.Level;
 import org.openjdk.jmh.annotations.Measurement;
 import org.openjdk.jmh.annotations.Mode;
 import org.openjdk.jmh.annotations.Scope;
 import org.openjdk.jmh.annotations.State;
 import org.openjdk.jmh.annotations.TearDown;
-import org.openjdk.jmh.annotations.Threads;
 import org.openjdk.jmh.annotations.Warmup;
 import org.openjdk.jmh.it.Fixtures;
 import org.openjdk.jmh.runner.Runner;
@@ -68,8 +68,8 @@
     @Warmup(iterations = 0)
     @Measurement(iterations = 2, time = 100, timeUnit = TimeUnit.MILLISECONDS)
     @Fork(1)
-    @Threads(2)
     @Group("group1")
+    @GroupThreads(2)
     public void test1() {
         Fixtures.work();
         visitors.add(Thread.currentThread());
@@ -80,8 +80,8 @@
     @Warmup(iterations = 0)
     @Measurement(iterations = 2, time = 100, timeUnit = TimeUnit.MILLISECONDS)
     @Fork(1)
-    @Threads(2)
     @Group("group1")
+    @GroupThreads(2)
     public void test2() {
         Fixtures.work();
         visitors.add(Thread.currentThread());
--- a/jmh-core-it/src/test/java/org/openjdk/jmh/it/sharing/GroupDefaultBenchSharingTest.java	Tue Nov 19 15:07:36 2013 +0400
+++ b/jmh-core-it/src/test/java/org/openjdk/jmh/it/sharing/GroupDefaultBenchSharingTest.java	Tue Nov 19 21:11:26 2013 +0400
@@ -30,13 +30,13 @@
 import org.openjdk.jmh.annotations.BenchmarkMode;
 import org.openjdk.jmh.annotations.Fork;
 import org.openjdk.jmh.annotations.GenerateMicroBenchmark;
+import org.openjdk.jmh.annotations.GroupThreads;
 import org.openjdk.jmh.annotations.Level;
 import org.openjdk.jmh.annotations.Measurement;
 import org.openjdk.jmh.annotations.Mode;
 import org.openjdk.jmh.annotations.Scope;
 import org.openjdk.jmh.annotations.State;
 import org.openjdk.jmh.annotations.TearDown;
-import org.openjdk.jmh.annotations.Threads;
 import org.openjdk.jmh.annotations.Warmup;
 import org.openjdk.jmh.it.Fixtures;
 import org.openjdk.jmh.runner.Runner;
@@ -67,7 +67,7 @@
     @Warmup(iterations = 0)
     @Measurement(iterations = 2, time = 100, timeUnit = TimeUnit.MILLISECONDS)
     @Fork(1)
-    @Threads(2)
+    @GroupThreads(2)
     public void test() {
         Fixtures.work();
         visitors.add(Thread.currentThread());
--- a/jmh-core-it/src/test/java/org/openjdk/jmh/it/sharing/GroupDefaultStateSharingTest.java	Tue Nov 19 15:07:36 2013 +0400
+++ b/jmh-core-it/src/test/java/org/openjdk/jmh/it/sharing/GroupDefaultStateSharingTest.java	Tue Nov 19 21:11:26 2013 +0400
@@ -30,13 +30,13 @@
 import org.openjdk.jmh.annotations.BenchmarkMode;
 import org.openjdk.jmh.annotations.Fork;
 import org.openjdk.jmh.annotations.GenerateMicroBenchmark;
+import org.openjdk.jmh.annotations.GroupThreads;
 import org.openjdk.jmh.annotations.Level;
 import org.openjdk.jmh.annotations.Measurement;
 import org.openjdk.jmh.annotations.Mode;
 import org.openjdk.jmh.annotations.Scope;
 import org.openjdk.jmh.annotations.State;
 import org.openjdk.jmh.annotations.TearDown;
-import org.openjdk.jmh.annotations.Threads;
 import org.openjdk.jmh.annotations.Warmup;
 import org.openjdk.jmh.it.Fixtures;
 import org.openjdk.jmh.runner.Runner;
@@ -69,7 +69,7 @@
     @Warmup(iterations = 0)
     @Measurement(iterations = 2, time = 100, timeUnit = TimeUnit.MILLISECONDS)
     @Fork(1)
-    @Threads(2)
+    @GroupThreads(2)
     public void test(MyState s) {
         Fixtures.work();
         s.visitors.add(Thread.currentThread());
--- a/jmh-core-it/src/test/java/org/openjdk/jmh/it/sharing/GroupStateSharingTest.java	Tue Nov 19 15:07:36 2013 +0400
+++ b/jmh-core-it/src/test/java/org/openjdk/jmh/it/sharing/GroupStateSharingTest.java	Tue Nov 19 21:11:26 2013 +0400
@@ -31,13 +31,13 @@
 import org.openjdk.jmh.annotations.Fork;
 import org.openjdk.jmh.annotations.GenerateMicroBenchmark;
 import org.openjdk.jmh.annotations.Group;
+import org.openjdk.jmh.annotations.GroupThreads;
 import org.openjdk.jmh.annotations.Level;
 import org.openjdk.jmh.annotations.Measurement;
 import org.openjdk.jmh.annotations.Mode;
 import org.openjdk.jmh.annotations.Scope;
 import org.openjdk.jmh.annotations.State;
 import org.openjdk.jmh.annotations.TearDown;
-import org.openjdk.jmh.annotations.Threads;
 import org.openjdk.jmh.annotations.Warmup;
 import org.openjdk.jmh.it.Fixtures;
 import org.openjdk.jmh.runner.Runner;
@@ -70,8 +70,8 @@
     @Warmup(iterations = 0)
     @Measurement(iterations = 2, time = 100, timeUnit = TimeUnit.MILLISECONDS)
     @Fork(1)
-    @Threads(2)
     @Group("group1")
+    @GroupThreads(2)
     public void test1(MyState s) {
         Fixtures.work();
         s.visitors.add(Thread.currentThread());
@@ -81,8 +81,8 @@
     @BenchmarkMode(Mode.All)
     @Warmup(iterations = 0)
     @Measurement(iterations = 2, time = 100, timeUnit = TimeUnit.MILLISECONDS)
-    @Threads(2)
     @Group("group1")
+    @GroupThreads(2)
     public void test2(MyState s) {
         Fixtures.work();
         s.visitors.add(Thread.currentThread());
--- a/jmh-core-it/src/test/java/org/openjdk/jmh/it/times/GroupBenchHelperTimesTest.java	Tue Nov 19 15:07:36 2013 +0400
+++ b/jmh-core-it/src/test/java/org/openjdk/jmh/it/times/GroupBenchHelperTimesTest.java	Tue Nov 19 21:11:26 2013 +0400
@@ -30,6 +30,7 @@
 import org.openjdk.jmh.annotations.BenchmarkMode;
 import org.openjdk.jmh.annotations.Fork;
 import org.openjdk.jmh.annotations.GenerateMicroBenchmark;
+import org.openjdk.jmh.annotations.GroupThreads;
 import org.openjdk.jmh.annotations.Level;
 import org.openjdk.jmh.annotations.Measurement;
 import org.openjdk.jmh.annotations.Mode;
@@ -37,7 +38,6 @@
 import org.openjdk.jmh.annotations.Setup;
 import org.openjdk.jmh.annotations.State;
 import org.openjdk.jmh.annotations.TearDown;
-import org.openjdk.jmh.annotations.Threads;
 import org.openjdk.jmh.annotations.Warmup;
 import org.openjdk.jmh.it.Fixtures;
 import org.openjdk.jmh.runner.Runner;
@@ -154,7 +154,7 @@
     @Warmup(iterations = 0)
     @Measurement(iterations = 2, time = 100, timeUnit = TimeUnit.MILLISECONDS)
     @Fork(1)
-    @Threads(2)
+    @GroupThreads(2)
     public void test() {
         Fixtures.work();
         countInvocations.incrementAndGet();
--- a/jmh-core-it/src/test/java/org/openjdk/jmh/it/times/GroupStateHelperTimesTest.java	Tue Nov 19 15:07:36 2013 +0400
+++ b/jmh-core-it/src/test/java/org/openjdk/jmh/it/times/GroupStateHelperTimesTest.java	Tue Nov 19 21:11:26 2013 +0400
@@ -30,6 +30,7 @@
 import org.openjdk.jmh.annotations.BenchmarkMode;
 import org.openjdk.jmh.annotations.Fork;
 import org.openjdk.jmh.annotations.GenerateMicroBenchmark;
+import org.openjdk.jmh.annotations.GroupThreads;
 import org.openjdk.jmh.annotations.Level;
 import org.openjdk.jmh.annotations.Measurement;
 import org.openjdk.jmh.annotations.Mode;
@@ -37,7 +38,6 @@
 import org.openjdk.jmh.annotations.Setup;
 import org.openjdk.jmh.annotations.State;
 import org.openjdk.jmh.annotations.TearDown;
-import org.openjdk.jmh.annotations.Threads;
 import org.openjdk.jmh.annotations.Warmup;
 import org.openjdk.jmh.it.Fixtures;
 import org.openjdk.jmh.runner.Runner;
@@ -158,7 +158,7 @@
     @Warmup(iterations = 0)
     @Measurement(iterations = 2, time = 100, timeUnit = TimeUnit.MILLISECONDS)
     @Fork(1)
-    @Threads(2)
+    @GroupThreads(2)
     public void test(MyState state) {
         Fixtures.work();
         state.countInvocations.incrementAndGet();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jmh-core/src/main/java/org/openjdk/jmh/annotations/GroupThreads.java	Tue Nov 19 21:11:26 2013 +0400
@@ -0,0 +1,46 @@
+/*
+ * 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;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * This annotation describes the default amount of threads
+ * for particular subgroup. See {@link Group} for more details.
+ */
+@Target({ElementType.METHOD,ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface GroupThreads {
+
+    /** Number of threads in this subgroup. */
+    int value() default 1;
+
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jmh-core/src/main/java/org/openjdk/jmh/logic/ThreadControl.java	Tue Nov 19 21:11:26 2013 +0400
@@ -0,0 +1,92 @@
+/*
+ * 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.logic;
+
+/**
+ * Thread-local control info.
+ */
+public class ThreadControl extends ThreadControlL3 {
+    public int markerEnd;
+
+    public ThreadControl(int group, int subgroup) {
+        super(group, subgroup);
+    }
+}
+
+class ThreadControlL0 {
+    public int markerBegin;
+}
+
+class ThreadControlL1 extends ThreadControlL0 {
+    private boolean p001, p002, p003, p004, p005, p006, p007, p008;
+    private boolean p011, p012, p013, p014, p015, p016, p017, p018;
+    private boolean p021, p022, p023, p024, p025, p026, p027, p028;
+    private boolean p031, p032, p033, p034, p035, p036, p037, p038;
+    private boolean p041, p042, p043, p044, p045, p046, p047, p048;
+    private boolean p051, p052, p053, p054, p055, p056, p057, p058;
+    private boolean p061, p062, p063, p064, p065, p066, p067, p068;
+    private boolean p071, p072, p073, p074, p075, p076, p077, p078;
+    private boolean p101, p102, p103, p104, p105, p106, p107, p108;
+    private boolean p111, p112, p113, p114, p115, p116, p117, p118;
+    private boolean p121, p122, p123, p124, p125, p126, p127, p128;
+    private boolean p131, p132, p133, p134, p135, p136, p137, p138;
+    private boolean p141, p142, p143, p144, p145, p146, p147, p148;
+    private boolean p151, p152, p153, p154, p155, p156, p157, p158;
+    private boolean p161, p162, p163, p164, p165, p166, p167, p168;
+    private boolean p171, p172, p173, p174, p175, p176, p177, p178;
+}
+
+class ThreadControlL2 extends ThreadControlL1 {
+    public final int group;
+    public final int subgroup;
+
+    public ThreadControlL2(int group, int subgroup) {
+        this.group = group;
+        this.subgroup = subgroup;
+    }
+}
+
+class ThreadControlL3 extends ThreadControlL2 {
+    private boolean q001, q002, q003, q004, q005, q006, q007, q008;
+    private boolean q011, q012, q013, q014, q015, q016, q017, q018;
+    private boolean q021, q022, q023, q024, q025, q026, q027, q028;
+    private boolean q031, q032, q033, q034, q035, q036, q037, q038;
+    private boolean q041, q042, q043, q044, q045, q046, q047, q048;
+    private boolean q051, q052, q053, q054, q055, q056, q057, q058;
+    private boolean q061, q062, q063, q064, q065, q066, q067, q068;
+    private boolean q071, q072, q073, q074, q075, q076, q077, q078;
+    private boolean q101, q102, q103, q104, q105, q106, q107, q108;
+    private boolean q111, q112, q113, q114, q115, q116, q117, q118;
+    private boolean q121, q122, q123, q124, q125, q126, q127, q128;
+    private boolean q131, q132, q133, q134, q135, q136, q137, q138;
+    private boolean q141, q142, q143, q144, q145, q146, q147, q148;
+    private boolean q151, q152, q153, q154, q155, q156, q157, q158;
+    private boolean q161, q162, q163, q164, q165, q166, q167, q168;
+    private boolean q171, q172, q173, q174, q175, q176, q177, q178;
+
+    public ThreadControlL3(int group, int subgroup) {
+        super(group, subgroup);
+    }
+}
--- a/jmh-core/src/main/java/org/openjdk/jmh/processor/internal/GenerateMicroBenchmarkProcessor.java	Tue Nov 19 15:07:36 2013 +0400
+++ b/jmh-core/src/main/java/org/openjdk/jmh/processor/internal/GenerateMicroBenchmarkProcessor.java	Tue Nov 19 21:11:26 2013 +0400
@@ -28,6 +28,7 @@
 import org.openjdk.jmh.annotations.Fork;
 import org.openjdk.jmh.annotations.GenerateMicroBenchmark;
 import org.openjdk.jmh.annotations.Group;
+import org.openjdk.jmh.annotations.GroupThreads;
 import org.openjdk.jmh.annotations.Measurement;
 import org.openjdk.jmh.annotations.Mode;
 import org.openjdk.jmh.annotations.OperationsPerInvocation;
@@ -38,6 +39,7 @@
 import org.openjdk.jmh.annotations.Warmup;
 import org.openjdk.jmh.logic.BlackHole;
 import org.openjdk.jmh.logic.InfraControl;
+import org.openjdk.jmh.logic.ThreadControl;
 import org.openjdk.jmh.logic.results.AverageTimeResult;
 import org.openjdk.jmh.logic.results.RawResults;
 import org.openjdk.jmh.logic.results.Result;
@@ -123,7 +125,7 @@
                         for (String method : info.methodGroups.keySet()) {
                             MethodGroup group = info.methodGroups.get(method);
                             for (Mode m : group.getModes()) {
-                                writer.println(info.userName + "." + method + ", " + info.generatedName + "." + method + ", " + m);
+                                writer.println(info.userName + "." + method + ", " + info.generatedName + "." + method + ", " + m + ", " + group.getThreads());
                             }
                         }
                     }
@@ -226,7 +228,7 @@
 
             group.addStrictFP(classStrictFP);
             group.addStrictFP(methodStrictFP);
-            group.addMethod(method, getThreads(method));
+            group.addMethod(method, (method.getAnnotation(GroupThreads.class) != null) ? method.getAnnotation(GroupThreads.class).value() : 1);
         }
 
         // enforce the default value
@@ -404,6 +406,7 @@
         writer.println("import " + Generated.class.getName() + ';');
         writer.println();
         writer.println("import " + InfraControl.class.getName() + ';');
+        writer.println("import " + ThreadControl.class.getName() + ';');
         writer.println("import " + BlackHole.class.getName() + ';');
         writer.println("import " + Result.class.getName() + ';');
         writer.println("import " + ThroughputResult.class.getName() + ';');
@@ -643,7 +646,11 @@
         }
 
         List<String> annotations = new ArrayList<String>();
-        annotations.add("@" + Threads.class.getSimpleName() + "(" + totalThreads + ")");
+
+        if (methodGroup.methods().size() == 1) {
+            // only honor this setting for non-@Group benchmarks
+            annotations.add("@" + Threads.class.getSimpleName() + "(" + totalThreads + ")");
+        }
         annotations = CollectionUtils.addIfNotNull(annotations, warmupAnn);
         annotations = CollectionUtils.addIfNotNull(annotations, measurementAnn);
         annotations = CollectionUtils.addIfNotNull(annotations, forkAnn);
@@ -664,23 +671,17 @@
     }
 
     private void generateThroughput(PrintWriter writer, Mode benchmarkKind, MethodGroup methodGroup, long opsPerInv, TimeUnit timeUnit, StateObjectHandler states) {
-        writer.println(ident(1) + "public Result " + methodGroup.getName() + "_" + benchmarkKind + "(InfraControl control) throws Throwable { ");
+        writer.println(ident(1) + "public Result " + methodGroup.getName() + "_" + benchmarkKind + "(InfraControl control, ThreadControl threadControl) throws Throwable { ");
         writer.println();
 
         methodProlog(writer, methodGroup);
 
         boolean isSingleMethod = (methodGroup.methods().size() == 1);
-        int threadTally = 0;
+        int subGroup = -1;
+        for (Element method : methodGroup.methods()) {
+            subGroup++;
 
-        for (Element method : methodGroup.methods()) {
-
-            // determine the sibling bounds
-            int threads = Math.max(isSingleMethod ? 1 : 0, methodGroup.getMethodThreads(method));
-            int loId = threadTally;
-            int hiId = threadTally + threads;
-            threadTally = hiId;
-
-            writer.println(ident(2) + "if (" + loId + " <= siblingId && siblingId < " + hiId + ") { ");
+            writer.println(ident(2) + "if (threadControl.subgroup == " + subGroup + ") { ");
 
             iterationProlog(writer, 3, method, states);
 
@@ -761,21 +762,16 @@
     }
 
     private void generateAverageTime(PrintWriter writer, Mode benchmarkKind, MethodGroup methodGroup, long opsPerInv, TimeUnit timeUnit, StateObjectHandler states) {
-        writer.println(ident(1) + "public Result " + methodGroup.getName() + "_" + benchmarkKind + "(InfraControl control) throws Throwable { ");
+        writer.println(ident(1) + "public Result " + methodGroup.getName() + "_" + benchmarkKind + "(InfraControl control, ThreadControl threadControl) throws Throwable { ");
 
         methodProlog(writer, methodGroup);
 
         boolean isSingleMethod = (methodGroup.methods().size() == 1);
-        int threadTally = 0;
+        int subGroup = -1;
         for (Element method : methodGroup.methods()) {
+            subGroup++;
 
-            // determine the sibling bounds
-            int threads = Math.max(isSingleMethod ? 1 : 0, methodGroup.getMethodThreads(method));
-            int loId = threadTally;
-            int hiId = threadTally + threads;
-            threadTally = hiId;
-
-            writer.println(ident(2) + "if (" + loId + " <= siblingId && siblingId < " + hiId + ") { ");
+            writer.println(ident(2) + "if (threadControl.subgroup == " + subGroup + ") { ");
 
             iterationProlog(writer, 3, method, states);
 
@@ -854,15 +850,7 @@
     }
 
     private void methodProlog(PrintWriter writer, MethodGroup methodGroup) {
-        writer.println(ident(2) + "if (!threadId_inited) {");
-        writer.println(ident(2) + "    threadId = threadSelector.getAndIncrement();");
-        writer.println(ident(2) + "    threadId_inited = true;");
-        writer.println(ident(2) + "}");
-
-        writer.println(ident(2) + "int groupThreadCount = " + Math.max(1, methodGroup.getTotalThreadCount()) + ";");
-        writer.println(ident(2) + "int groupId = threadId / groupThreadCount;");
-        writer.println(ident(2) + "int siblingId = threadId % groupThreadCount;");
-        writer.println();
+        // do nothing
     }
 
     private String prefix(String argList) {
@@ -874,21 +862,17 @@
     }
 
     private void generateSampleTime(PrintWriter writer, Mode benchmarkKind, MethodGroup methodGroup, TimeUnit timeUnit, StateObjectHandler states) {
-        writer.println(ident(1) + "public Result " + methodGroup.getName() + "_" + benchmarkKind + "(InfraControl control) throws Throwable { ");
+        writer.println(ident(1) + "public Result " + methodGroup.getName() + "_" + benchmarkKind + "(InfraControl control, ThreadControl threadControl) throws Throwable { ");
         writer.println();
 
         methodProlog(writer, methodGroup);
 
         boolean isSingleMethod = (methodGroup.methods().size() == 1);
-        int threadTally = 0;
+        int subGroup = -1;
         for (Element method : methodGroup.methods()) {
-            // determine the sibling bounds
-            int threads = Math.max(isSingleMethod ? 1 : 0, methodGroup.getMethodThreads(method));
-            int loId = threadTally;
-            int hiId = threadTally + threads;
-            threadTally = hiId;
+            subGroup++;
 
-            writer.println(ident(2) + "if (" + loId + " <= siblingId && siblingId < " + hiId + ") { ");
+            writer.println(ident(2) + "if (threadControl.subgroup == " + subGroup + ") { ");
 
             iterationProlog(writer, 3, method, states);
 
@@ -980,23 +964,18 @@
     }
 
     private void generateSingleShotTime(PrintWriter writer, Mode benchmarkKind, MethodGroup methodGroup, TimeUnit timeUnit, StateObjectHandler states) {
-        writer.println(ident(1) + "public Result " + methodGroup.getName() + "_" + benchmarkKind + "(InfraControl control) throws Throwable { ");
+        writer.println(ident(1) + "public Result " + methodGroup.getName() + "_" + benchmarkKind + "(InfraControl control, ThreadControl threadControl) throws Throwable { ");
 
         methodProlog(writer, methodGroup);
 
         writer.println(ident(2) + "long realTime = 0;");
 
         boolean isSingleMethod = (methodGroup.methods().size() == 1);
-        int threadTally = 0;
+        int subGroup = -1;
         for (Element method : methodGroup.methods()) {
+            subGroup++;
 
-            // determine the sibling bounds
-            int threads = Math.max(isSingleMethod ? 1 : 0, methodGroup.getMethodThreads(method));
-            int loId = threadTally;
-            int hiId = threadTally + threads;
-            threadTally = hiId;
-
-            writer.println(ident(2) + "if (" + loId + " <= siblingId && siblingId < " + hiId + ") { ");
+            writer.println(ident(2) + "if (threadControl.subgroup == " + subGroup + ") { ");
 
             iterationProlog(writer, 3, method, states);
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jmh-core/src/main/java/org/openjdk/jmh/processor/internal/GroupValidationProcessor.java	Tue Nov 19 21:11:26 2013 +0400
@@ -0,0 +1,74 @@
+/*
+ * 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.processor.internal;
+
+import org.openjdk.jmh.annotations.Group;
+import org.openjdk.jmh.annotations.GroupThreads;
+import org.openjdk.jmh.annotations.Threads;
+
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.RoundEnvironment;
+import javax.annotation.processing.SupportedSourceVersion;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.TypeElement;
+import javax.tools.Diagnostic.Kind;
+import java.util.HashSet;
+import java.util.Set;
+
+@SupportedSourceVersion(SourceVersion.RELEASE_6)
+public class GroupValidationProcessor extends AbstractProcessor {
+
+    @Override
+    public Set<String> getSupportedAnnotationTypes() {
+        Set<String> supported = new HashSet<String>();
+        supported.add(Group.class.getName());
+        return supported;
+    }
+
+    @Override
+    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+        try {
+            for (TypeElement annotation : annotations) {
+                for (Element element : roundEnv.getElementsAnnotatedWith(annotation)) {
+
+                    if (element.getAnnotation(Threads.class) != null) {
+                        processingEnv.getMessager().printMessage(Kind.ERROR,
+                                "@" + Threads.class.getSimpleName() + " annotation is placed within " +
+                                        "the benchmark method with @" + Group.class.getSimpleName() + " annotation. " +
+                                        "This has ambiguous behavioral effect, and prohibited. " +
+                                "Did you mean @" + GroupThreads.class.getSimpleName() + " instead?",
+                                element);
+                    }
+                }
+            }
+        } catch (Throwable t) {
+            processingEnv.getMessager().printMessage(Kind.ERROR, "Annotation processor had throw exception: " + t);
+            t.printStackTrace(System.err);
+        }
+        return true;
+    }
+
+}
--- a/jmh-core/src/main/java/org/openjdk/jmh/processor/internal/MethodGroup.java	Tue Nov 19 15:07:36 2013 +0400
+++ b/jmh-core/src/main/java/org/openjdk/jmh/processor/internal/MethodGroup.java	Tue Nov 19 21:11:26 2013 +0400
@@ -31,8 +31,8 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.EnumSet;
-import java.util.LinkedHashSet;
 import java.util.Set;
+import java.util.TreeSet;
 
 public class MethodGroup implements Comparable<MethodGroup> {
     private final String name;
@@ -42,7 +42,7 @@
 
     MethodGroup(String name) {
         this.name = name;
-        this.methods = new LinkedHashSet<MethodInvocation>();
+        this.methods = new TreeSet<MethodInvocation>();
         this.modes = EnumSet.noneOf(Mode.class);
     }
 
@@ -88,15 +88,6 @@
         return threadCount;
     }
 
-    public int getMethodThreads(Element method) {
-        for (MethodInvocation m : methods) {
-            if (m.element.equals(method)) {
-                return m.threads;
-            }
-        }
-        throw new IllegalStateException("");
-    }
-
     public String getName() {
         return name;
     }
@@ -120,4 +111,13 @@
     public Set<Mode> getModes() {
         return modes;
     }
+
+    public String getThreads() {
+        StringBuilder sb = new StringBuilder();
+        for (MethodInvocation mi : methods) {
+            sb.append(mi.threads);
+            sb.append("=");
+        }
+        return sb.toString();
+    }
 }
--- a/jmh-core/src/main/java/org/openjdk/jmh/processor/internal/MethodInvocation.java	Tue Nov 19 15:07:36 2013 +0400
+++ b/jmh-core/src/main/java/org/openjdk/jmh/processor/internal/MethodInvocation.java	Tue Nov 19 21:11:26 2013 +0400
@@ -26,7 +26,7 @@
 
 import javax.lang.model.element.Element;
 
-public class MethodInvocation {
+public class MethodInvocation implements Comparable<MethodInvocation> {
     public final Element element;
     public final int threads;
 
@@ -34,4 +34,9 @@
         this.element = element;
         this.threads = threads;
     }
+
+    @Override
+    public int compareTo(MethodInvocation o) {
+        return element.getSimpleName().toString().compareTo(o.element.getSimpleName().toString());
+    }
 }
--- a/jmh-core/src/main/java/org/openjdk/jmh/processor/internal/StateObjectHandler.java	Tue Nov 19 15:07:36 2013 +0400
+++ b/jmh-core/src/main/java/org/openjdk/jmh/processor/internal/StateObjectHandler.java	Tue Nov 19 21:11:26 2013 +0400
@@ -41,6 +41,7 @@
 import javax.lang.model.util.ElementFilter;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -372,20 +373,23 @@
             result.add("static java.util.Map<Integer, " + so.type + "> " + so.fieldIdentifier + "_map = java.util.Collections.synchronizedMap(new java.util.HashMap<Integer, " + so.type + ">());");
             result.add("");
             result.add(so.type + " tryInit_" + so.fieldIdentifier + "(int groupId, " + so.type + " val) throws Throwable {");
-            result.add("    if (!" + so.fieldIdentifier + "_map.containsKey(groupId)) {");
-            result.add("        synchronized(this.getClass()) {");
-            result.add("            if (!" + so.fieldIdentifier + "_map.containsKey(groupId)) {");
+            result.add("    synchronized(this.getClass()) {");
+            result.add("        " + so.type + " local = " + so.fieldIdentifier + "_map.get(groupId);");
+            result.add("        if (local == null) {");
+            result.add("            " + so.fieldIdentifier + "_map.put(groupId, val);");
+            result.add("            local = val;");
+            result.add("        }");
+            result.add("        if (!local.ready" + Level.Trial + ") {");
             for (HelperMethodInvocation hmi : helpersByState.get(so)) {
                 if (hmi.helperLevel != Level.Trial) continue;
                 if (hmi.type != HelperType.SETUP) continue;
-                result.add("                val." + hmi.name + "();");
+                result.add("            local." + hmi.name + "();");
             }
-            result.add("                " + "val.ready" + Level.Trial + " = true;");
-            result.add("                " + so.fieldIdentifier + "_map.put(groupId, val);");
-            result.add("            }");
+            result.add("            " + "local.ready" + Level.Trial + " = true;");
+            result.add("            " + so.fieldIdentifier + "_map.put(groupId, val);");
             result.add("        }");
+            result.add("        return local;");
             result.add("    }");
-            result.add("    return " + so.fieldIdentifier + "_map.get(groupId);");
             result.add("}");
         }
         return result;
@@ -400,7 +404,7 @@
                     result.add(so.type + " " + so.localIdentifier + " = tryInit_" + so.fieldIdentifier + "(new " + so.type + "());");
                     break;
                 case Group:
-                    result.add(so.type + " " + so.localIdentifier + " = tryInit_" + so.fieldIdentifier + "(groupId, new " + so.type + "());");
+                    result.add(so.type + " " + so.localIdentifier + " = tryInit_" + so.fieldIdentifier + "(threadControl.group, new " + so.type + "());");
                     break;
                 default:
                     throw new IllegalStateException("Unhandled scope: " + so.scope);
@@ -478,11 +482,7 @@
     }
 
     public Collection<String> getFields() {
-        Collection<String> result = new ArrayList<String>();
-        result.add("public static final java.util.concurrent.atomic.AtomicInteger threadSelector = new java.util.concurrent.atomic.AtomicInteger();");
-        result.add("public int threadId = 0;");
-        result.add("public boolean threadId_inited = false;");
-        return result;
+        return Collections.emptyList();
     }
 
     private String collapseTypeName(String e) {
--- a/jmh-core/src/main/java/org/openjdk/jmh/runner/BenchmarkRecord.java	Tue Nov 19 15:07:36 2013 +0400
+++ b/jmh-core/src/main/java/org/openjdk/jmh/runner/BenchmarkRecord.java	Tue Nov 19 21:11:26 2013 +0400
@@ -33,31 +33,53 @@
     private final String userName;
     private final String generatedName;
     private final Mode mode;
+    private final int[] threadGroups;
 
-    public BenchmarkRecord(String userName, String generatedName, Mode mode) {
+    public BenchmarkRecord(String userName, String generatedName, Mode mode, int... threadGroups) {
         this.userName = userName;
         this.generatedName = generatedName;
         this.mode = mode;
+        this.threadGroups = threadGroups;
     }
 
     public BenchmarkRecord(String line) {
         String[] args = line.split(",");
 
-        if (args.length != 3) {
+        if (args.length != 4) {
             throw new IllegalStateException("Mismatched format for the line: " + line);
         }
 
         this.userName = args[0].trim();
         this.generatedName = args[1].trim();
         this.mode = Mode.deepValueOf(args[2].trim());
+        this.threadGroups = convert(args[3].split("="));
+    }
+
+    private int[] convert(String[] ss) {
+        int[] arr = new int[ss.length];
+        int cnt = 0;
+        for (String s : ss) {
+            arr[cnt] = Integer.valueOf(s.trim());
+            cnt++;
+        }
+        return arr;
+    }
+
+    private String convert(int[] arr) {
+        StringBuilder sb = new StringBuilder();
+        for (int i : arr) {
+            sb.append(i);
+            sb.append("=");
+        }
+        return sb.toString();
     }
 
     public String toLine() {
-        return userName + "," + generatedName + "," + mode;
+        return userName + "," + generatedName + "," + mode + "," + convert(threadGroups);
     }
 
     public BenchmarkRecord cloneWith(Mode mode) {
-        return new BenchmarkRecord(userName, generatedName, mode);
+        return new BenchmarkRecord(userName, generatedName, mode, threadGroups);
     }
 
     @Override
@@ -116,6 +138,10 @@
         return mode;
     }
 
+    public int[] getThreadGroups() {
+        return threadGroups;
+    }
+
     @Override
     public String toString() {
         return "BenchmarkRecord{" +
--- a/jmh-core/src/main/java/org/openjdk/jmh/runner/LoopMicroBenchmarkHandler.java	Tue Nov 19 15:07:36 2013 +0400
+++ b/jmh-core/src/main/java/org/openjdk/jmh/runner/LoopMicroBenchmarkHandler.java	Tue Nov 19 21:11:26 2013 +0400
@@ -27,6 +27,7 @@
 
 import org.openjdk.jmh.annotations.Mode;
 import org.openjdk.jmh.logic.InfraControl;
+import org.openjdk.jmh.logic.ThreadControl;
 import org.openjdk.jmh.logic.results.IterationResult;
 import org.openjdk.jmh.logic.results.Result;
 import org.openjdk.jmh.output.format.OutputFormat;
@@ -86,8 +87,24 @@
 
         // preparing the worker runnables
         BenchmarkTask[] runners = new BenchmarkTask[numThreads];
+
+        int[] groups = params.getThreadGroups();
+        int currentGroup = 0;
+        int currentSubgroup = 0;
+        int remainingSubgroupThreads = groups[currentSubgroup];
         for (int i = 0; i < runners.length; i++) {
-            runners[i] = new BenchmarkTask(threadLocal, control);
+            while (remainingSubgroupThreads == 0) {
+                currentSubgroup++;
+                if (currentSubgroup == groups.length) {
+                    currentSubgroup = 0;
+                    currentGroup++;
+                }
+                remainingSubgroupThreads = groups[currentSubgroup];
+            }
+            remainingSubgroupThreads--;
+
+            ThreadControl threadControl = new ThreadControl(currentGroup, currentSubgroup);
+            runners[i] = new BenchmarkTask(threadLocal, control, threadControl);
         }
 
         // submit tasks to threadpool
@@ -173,16 +190,18 @@
 
         private final ThreadLocal<InstanceProvider> invocationHandler;
         private final InfraControl control;
+        private final ThreadControl threadControl;
 
-        BenchmarkTask(ThreadLocal<InstanceProvider> invocationHandler, InfraControl control) {
+        BenchmarkTask(ThreadLocal<InstanceProvider> invocationHandler, InfraControl control, ThreadControl threadControl) {
             this.invocationHandler = invocationHandler;
             this.control = control;
+            this.threadControl = threadControl;
         }
 
         @Override
         public Result call() throws Exception {
             try {
-                return invokeBenchmark(invocationHandler.get().getInstance(), control);
+                return invokeBenchmark(invocationHandler.get().getInstance(), control, threadControl);
             } catch (Throwable e) {
                 // about to fail the iteration;
                 // compensate for missed sync-iteration latches, we don't care about that anymore
@@ -210,11 +229,11 @@
         /**
          * Helper method for running the benchmark in a given instance.
          */
-        private Result invokeBenchmark(Object instance, InfraControl control) throws Throwable {
+        private Result invokeBenchmark(Object instance, InfraControl control, ThreadControl threadControl) throws Throwable {
             Result result;
             if (method != null) {
                 try {
-                    result = (Result) method.invoke(instance, control);
+                    result = (Result) method.invoke(instance, control, threadControl);
                 } catch (IllegalAccessException e) {
                     throw new RuntimeException("Can't invoke " + method.getDeclaringClass().getName() + "." + method.getName(), e);
                 } catch (InvocationTargetException e) {
--- a/jmh-core/src/main/java/org/openjdk/jmh/runner/MicroBenchmarkHandlers.java	Tue Nov 19 15:07:36 2013 +0400
+++ b/jmh-core/src/main/java/org/openjdk/jmh/runner/MicroBenchmarkHandlers.java	Tue Nov 19 21:11:26 2013 +0400
@@ -25,6 +25,7 @@
 package org.openjdk.jmh.runner;
 
 import org.openjdk.jmh.logic.InfraControl;
+import org.openjdk.jmh.logic.ThreadControl;
 import org.openjdk.jmh.logic.results.Result;
 import org.openjdk.jmh.output.format.OutputFormat;
 import org.openjdk.jmh.runner.options.Options;
@@ -83,7 +84,7 @@
         }
         final Class<?>[] parameterTypes = m.getParameterTypes();
 
-        if (parameterTypes.length != 1) {
+        if (parameterTypes.length != 2) {
             return false;
         }
 
@@ -91,6 +92,10 @@
             return false;
         }
 
+        if (parameterTypes[1] != ThreadControl.class) {
+            return false;
+        }
+
         return true;
     }
 
--- a/jmh-core/src/main/java/org/openjdk/jmh/runner/options/ChainedOptionsBuilder.java	Tue Nov 19 15:07:36 2013 +0400
+++ b/jmh-core/src/main/java/org/openjdk/jmh/runner/options/ChainedOptionsBuilder.java	Tue Nov 19 21:11:26 2013 +0400
@@ -122,6 +122,13 @@
     ChainedOptionsBuilder threads(int count);
 
     /**
+     * Subgroups thread distribution.
+     * @param groups thread distribution
+     * @return builder
+     */
+    ChainedOptionsBuilder threadGroups(int... groups);
+
+    /**
      * Should synchronize measurementIterations?
      * @param value flag
      * @return builder
--- a/jmh-core/src/main/java/org/openjdk/jmh/runner/options/CommandLineOptions.java	Tue Nov 19 15:07:36 2013 +0400
+++ b/jmh-core/src/main/java/org/openjdk/jmh/runner/options/CommandLineOptions.java	Tue Nov 19 21:11:26 2013 +0400
@@ -36,6 +36,7 @@
 import org.openjdk.jmh.runner.options.handlers.BooleanOptionHandler;
 import org.openjdk.jmh.runner.options.handlers.ForkOptionHandler;
 import org.openjdk.jmh.runner.options.handlers.ProfilersOptionHandler;
+import org.openjdk.jmh.runner.options.handlers.ThreadCountsOptionHandler;
 import org.openjdk.jmh.runner.options.handlers.ThreadsOptionHandler;
 import org.openjdk.jmh.runner.options.handlers.TimeUnitOptionHandler;
 import org.openjdk.jmh.runner.options.handlers.TimeValueOptionHandler;
@@ -93,6 +94,9 @@
     @Option(name = "-t", aliases = {"--threads"}, usage = "Number of threads to run the microbenchmark with. Special value \"max\" will use Runtime.availableProcessors()", handler = ThreadsOptionHandler.class)
     protected int threads = Integer.MIN_VALUE;
 
+    @Option(name = "-tg", aliases = {"--threadGroups"}, usage = "Thread group distribution", handler = ThreadCountsOptionHandler.class)
+    protected List<Integer> threadGroups = new ArrayList<Integer>();
+
     @Option(name = "-si", aliases = {"--synciterations"}, usage = "Should the harness continue to load each thread with work untill all threads are done with their measured work? Default is " + Defaults.SHOULD_SYNCH_ITERATIONS, handler = BooleanOptionHandler.class)
     protected Boolean synchIterations = null; // true
 
@@ -423,6 +427,19 @@
         return threads;
     }
 
+    @Override
+    public int[] getThreadGroups() {
+        if (threadGroups.isEmpty()) {
+            return new int[] { 1 };
+        } else {
+            int[] r = new int[threadGroups.size()];
+            for (int c = 0; c < r.length; c++) {
+                r[c] = threadGroups.get(c);
+            }
+            return r;
+        }
+    }
+
     /**
      * Getter
      *
--- a/jmh-core/src/main/java/org/openjdk/jmh/runner/options/Options.java	Tue Nov 19 15:07:36 2013 +0400
+++ b/jmh-core/src/main/java/org/openjdk/jmh/runner/options/Options.java	Tue Nov 19 21:11:26 2013 +0400
@@ -104,6 +104,12 @@
     int getThreads();
 
     /**
+     * Thread subgroups distribution.
+     * @return array of thread ratios
+     */
+    int[] getThreadGroups();
+
+    /**
      * Should synchronize iterations?
      * TODO: Rework "null" interface?
      * @return should we? "null" if not defined
--- a/jmh-core/src/main/java/org/openjdk/jmh/runner/options/OptionsBuilder.java	Tue Nov 19 15:07:36 2013 +0400
+++ b/jmh-core/src/main/java/org/openjdk/jmh/runner/options/OptionsBuilder.java	Tue Nov 19 21:11:26 2013 +0400
@@ -222,6 +222,21 @@
 
     // ---------------------------------------------------------------------------
 
+    private int[] threadGroups = new int[] {1};
+
+    @Override
+    public ChainedOptionsBuilder threadGroups(int... groups) {
+        this.threadGroups = groups;
+        return this;
+    }
+
+    @Override
+    public int[] getThreadGroups() {
+        return threadGroups;
+    }
+
+    // ---------------------------------------------------------------------------
+
     private Boolean syncIterations;
 
     @Override
--- a/jmh-core/src/main/java/org/openjdk/jmh/runner/parameters/BenchmarkParams.java	Tue Nov 19 15:07:36 2013 +0400
+++ b/jmh-core/src/main/java/org/openjdk/jmh/runner/parameters/BenchmarkParams.java	Tue Nov 19 21:11:26 2013 +0400
@@ -31,6 +31,7 @@
 import org.openjdk.jmh.annotations.Warmup;
 import org.openjdk.jmh.runner.BenchmarkRecord;
 import org.openjdk.jmh.runner.options.Options;
+import org.openjdk.jmh.util.Utils;
 
 import java.io.Serializable;
 import java.lang.reflect.Method;
@@ -45,13 +46,16 @@
             threads = Runtime.getRuntime().availableProcessors();
         }
 
+        int[] threadGroups = getThreadGroups(options.getThreadGroups(), benchmark.getThreadGroups());
+        threads = Utils.roundUp(threads, Utils.sum(threadGroups));
+
         IterationParams measurement = doMeasurement ?
-                getMeasurement(options, benchmark, method, threads) :
-                new IterationParams(0, TimeValue.NONE, 1);
+                getMeasurement(options, benchmark, method, threads, threadGroups) :
+                new IterationParams(0, TimeValue.NONE, 1, 1);
 
         IterationParams warmup = doWarmup ?
-                getWarmup(options, benchmark, method, threads) :
-                new IterationParams(0, TimeValue.NONE, 1);
+                getWarmup(options, benchmark, method, threads, threadGroups) :
+                new IterationParams(0, TimeValue.NONE, 1, 1);
 
         return new BenchmarkParams(
                 shouldSynchIterations,
@@ -59,7 +63,7 @@
                 threads);
     }
 
-    private static IterationParams getWarmup(Options options, BenchmarkRecord benchmark, Method method, int threads) {
+    private static IterationParams getWarmup(Options options, BenchmarkRecord benchmark, Method method, int threads, int[] threadGroups) {
         boolean isSingleShot = (benchmark.getMode() == Mode.SingleShotTime);
         Warmup warAnn = method.getAnnotation(Warmup.class);
         int iters = (warAnn == null) ? -1 : warAnn.iterations();
@@ -67,7 +71,8 @@
             return new IterationParams(
                     getInteger(options.getWarmupIterations(), iters, Defaults.SINGLE_SHOT_WARMUP_COUNT),
                     TimeValue.NONE,
-                    threads);
+                    threads,
+                    threadGroups);
         } else {
             TimeValue timeValue = options.getWarmupTime();
             if (timeValue == null || timeValue.getTime() == -1) {
@@ -77,11 +82,15 @@
                     timeValue = new TimeValue(warAnn.time(), warAnn.timeUnit());
                 }
             }
-            return new IterationParams(getInteger(options.getWarmupIterations(), iters, Defaults.WARMUP_ITERATION_COUNT), timeValue, threads);
+            return new IterationParams(
+                    getInteger(options.getWarmupIterations(), iters, Defaults.WARMUP_ITERATION_COUNT),
+                    timeValue,
+                    threads,
+                    threadGroups);
         }
     }
 
-    private static IterationParams getMeasurement(Options options, BenchmarkRecord benchmark, Method method, int threads) {
+    private static IterationParams getMeasurement(Options options, BenchmarkRecord benchmark, Method method, int threads, int[] threadGroups) {
         boolean isSingleShot = (benchmark.getMode() == Mode.SingleShotTime);
         Measurement meAnn = method.getAnnotation(Measurement.class);
         int iters = (meAnn == null) ? -1 : meAnn.iterations();
@@ -89,7 +98,8 @@
             return new IterationParams(
                     getInteger(options.getIterations(), iters, Defaults.SINGLE_SHOT_ITERATION_COUNT),
                     TimeValue.NONE,
-                    threads);
+                    threads,
+                    threadGroups);
 
         } else {
             TimeValue timeValue = options.getRuntime();
@@ -101,7 +111,18 @@
                 }
             }
             return new IterationParams(
-                    getInteger(options.getIterations(), iters, Defaults.MEASUREMENT_ITERATION_COUNT), timeValue, threads);
+                    getInteger(options.getIterations(), iters, Defaults.MEASUREMENT_ITERATION_COUNT),
+                    timeValue,
+                    threads,
+                    threadGroups);
+        }
+    }
+
+    private static int[] getThreadGroups(int[] first, int[] second) {
+        if (first.length == 1 && first[0] == 1) {
+            return second;
+        } else {
+            return first;
         }
     }
 
--- a/jmh-core/src/main/java/org/openjdk/jmh/runner/parameters/IterationParams.java	Tue Nov 19 15:07:36 2013 +0400
+++ b/jmh-core/src/main/java/org/openjdk/jmh/runner/parameters/IterationParams.java	Tue Nov 19 21:11:26 2013 +0400
@@ -25,6 +25,7 @@
 package org.openjdk.jmh.runner.parameters;
 
 import java.io.Serializable;
+import java.util.Arrays;
 
 /**
  * @author sergey.kuksenko@oracle.com
@@ -46,10 +47,16 @@
      */
     private final int threads;
 
-    public IterationParams(int count, TimeValue time, int threads) {
+    /**
+     * Subgroups distribution
+     */
+    private final int[] threadGroups;
+
+    public IterationParams(int count, TimeValue time, int threads, int... threadGroups) {
         this.count = count;
         this.timeValue = time;
         this.threads = threads;
+        this.threadGroups = threadGroups;
     }
 
     public int getCount() {
@@ -61,33 +68,27 @@
     }
 
     @Override
-    public int hashCode() {
-        int hash = 7;
-        hash = 83 * hash + this.count;
-        hash = 83 * hash + this.threads;
-        hash = 83 * hash + (this.timeValue != null ? this.timeValue.hashCode() : 0);
-        return hash;
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        IterationParams that = (IterationParams) o;
+
+        if (count != that.count) return false;
+        if (threads != that.threads) return false;
+        if (!Arrays.equals(threadGroups, that.threadGroups)) return false;
+        if (timeValue != null ? !timeValue.equals(that.timeValue) : that.timeValue != null) return false;
+
+        return true;
     }
 
     @Override
-    public boolean equals(Object obj) {
-        if (obj == null) {
-            return false;
-        }
-        if (getClass() != obj.getClass()) {
-            return false;
-        }
-        final IterationParams other = (IterationParams) obj;
-        if (this.count != other.count) {
-            return false;
-        }
-        if (this.threads != other.threads) {
-            return false;
-        }
-        if (this.timeValue != other.timeValue && (this.timeValue == null || !this.timeValue.equals(other.timeValue))) {
-            return false;
-        }
-        return true;
+    public int hashCode() {
+        int result = count;
+        result = 31 * result + (timeValue != null ? timeValue.hashCode() : 0);
+        result = 31 * result + threads;
+        result = 31 * result + (threadGroups != null ? Arrays.hashCode(threadGroups) : 0);
+        return result;
     }
 
     @Override
@@ -98,4 +99,8 @@
     public int getThreads() {
         return threads;
     }
+
+    public int[] getThreadGroups() {
+        return threadGroups;
+    }
 }
--- a/jmh-core/src/main/java/org/openjdk/jmh/util/Utils.java	Tue Nov 19 15:07:36 2013 +0400
+++ b/jmh-core/src/main/java/org/openjdk/jmh/util/Utils.java	Tue Nov 19 21:11:26 2013 +0400
@@ -52,4 +52,20 @@
         }
         return sb.toString();
     }
+
+    public static int sum(int[] arr) {
+        int sum = 0;
+        for (int i : arr) {
+            sum += i;
+        }
+        return sum;
+    }
+
+    public static int roundUp(int v, int quant) {
+        if ((v % quant) == 0) {
+            return v;
+        } else {
+            return ((v / quant) + 1)*quant;
+        }
+    }
 }
--- a/jmh-core/src/main/resources/META-INF/services/javax.annotation.processing.Processor	Tue Nov 19 15:07:36 2013 +0400
+++ b/jmh-core/src/main/resources/META-INF/services/javax.annotation.processing.Processor	Tue Nov 19 21:11:26 2013 +0400
@@ -23,4 +23,5 @@
 #
 org.openjdk.jmh.processor.internal.GenerateMicroBenchmarkProcessor
 org.openjdk.jmh.processor.internal.HelperMethodValidationProcessor
+org.openjdk.jmh.processor.internal.GroupValidationProcessor
 org.openjdk.jmh.processor.internal.CompilerControlProcessor
--- a/jmh-core/src/test/java/org/openjdk/jmh/logic/results/TestAggregateResult.java	Tue Nov 19 15:07:36 2013 +0400
+++ b/jmh-core/src/test/java/org/openjdk/jmh/logic/results/TestAggregateResult.java	Tue Nov 19 21:11:26 2013 +0400
@@ -46,7 +46,7 @@
 
     @BeforeClass
     public static void setupClass() {
-        result = new IterationResult(new BenchmarkRecord("blah,blah," + Mode.AverageTime), new IterationParams(1, TimeValue.days(1), 1));
+        result = new IterationResult(new BenchmarkRecord("blah", "blah", Mode.AverageTime, 1), new IterationParams(1, TimeValue.days(1), 1));
         for (double d : values) {
             result.addResult(new ThroughputResult(ResultRole.BOTH, "test1", (long) d, 10 * 1000 * 1000));
         }
--- a/jmh-core/src/test/resources/org/openjdk/jmh/runner/MicroBenchmarks	Tue Nov 19 15:07:36 2013 +0400
+++ b/jmh-core/src/test/resources/org/openjdk/jmh/runner/MicroBenchmarks	Tue Nov 19 21:11:26 2013 +0400
@@ -22,23 +22,23 @@
 #    questions.
 #
 
-oracle.micro.benchmarks.api.java.util.concurrent.NormalMaps.testConcurrentHashMap, xxx, AverageTime
-oracle.micro.benchmarks.app.jbb05.GeneratedSPECjbb2005HashMap.jbb2005HashMapGetInt, xxx, AverageTime
-oracle.micro.benchmarks.app.jbb05.GeneratedSPECjbb2005HashMap.jbb2005HashMapGetIntGC, xxx, AverageTime
-oracle.micro.benchmarks.app.jbb05.GeneratedSPECjbb2005HashMap.jbb2005HashMapGetInteger, xxx, AverageTime
-oracle.micro.benchmarks.app.jbb05.GeneratedSPECjbb2005HashMap.jbb2005ResizedHashMapGetInt, xxx, AverageTime
-oracle.micro.benchmarks.app.jbb05.GeneratedSPECjbb2005HashMap.jbb2005ResizedHashMapGetInteger, xxx, AverageTime
-oracle.micro.benchmarks.app.sjent.GeneratedPrintBase64.printBase64Binary_128bytes, xxx, AverageTime
-oracle.micro.benchmarks.app.sjent.GeneratedPrintBase64.printBase64Binary_32bytes, xxx, AverageTime
-oracle.micro.benchmarks.app.sjent.GeneratedPrintBase64.printBase64Binary_512bytes, xxx, AverageTime
-oracle.micro.benchmarks.api.java.util.concurrent.GeneratedMaps.testConcurrentHashMap, xxx, AverageTime
-org.openjdk.jmh.runner.TestMicro.dummy, xxx, AverageTime
-org.openjdk.jmh.runner.TestMicro.dummyWarm, xxx, AverageTime
-org.openjdk.jmh.runner.TestMicro.dummyWarmOnly, xxx, AverageTime
-org.openjdk.jmh.runner.TestMicro.dummySetupPayload, xxx, AverageTime
-org.openjdk.jmh.runner.TestMicro.boom_Exception, xxx, AverageTime
-org.openjdk.jmh.runner.TestMicro.boom_Error, xxx, AverageTime
-org.openjdk.jmh.runner.TestMicro.boom_Throwable, xxx, AverageTime
-org.openjdk.jmh.runner.TestMicro.blackholed, xxx, AverageTime
-org.openjdk.jmh.runner.TestBrokenMicro.dummyPayload, xxx, AverageTime
-org.openjdk.jmh.runner.TestExceptionThrowingMicro.ouch, xxx, AverageTime
\ No newline at end of file
+oracle.micro.benchmarks.api.java.util.concurrent.NormalMaps.testConcurrentHashMap, xxx, AverageTime, 1=
+oracle.micro.benchmarks.app.jbb05.GeneratedSPECjbb2005HashMap.jbb2005HashMapGetInt, xxx, AverageTime, 1=
+oracle.micro.benchmarks.app.jbb05.GeneratedSPECjbb2005HashMap.jbb2005HashMapGetIntGC, xxx, AverageTime, 1=
+oracle.micro.benchmarks.app.jbb05.GeneratedSPECjbb2005HashMap.jbb2005HashMapGetInteger, xxx, AverageTime, 1=
+oracle.micro.benchmarks.app.jbb05.GeneratedSPECjbb2005HashMap.jbb2005ResizedHashMapGetInt, xxx, AverageTime, 1=
+oracle.micro.benchmarks.app.jbb05.GeneratedSPECjbb2005HashMap.jbb2005ResizedHashMapGetInteger, xxx, AverageTime, 1=
+oracle.micro.benchmarks.app.sjent.GeneratedPrintBase64.printBase64Binary_128bytes, xxx, AverageTime, 1=
+oracle.micro.benchmarks.app.sjent.GeneratedPrintBase64.printBase64Binary_32bytes, xxx, AverageTime, 1=
+oracle.micro.benchmarks.app.sjent.GeneratedPrintBase64.printBase64Binary_512bytes, xxx, AverageTime, 1=
+oracle.micro.benchmarks.api.java.util.concurrent.GeneratedMaps.testConcurrentHashMap, xxx, AverageTime, 1=
+org.openjdk.jmh.runner.TestMicro.dummy, xxx, AverageTime, 1=
+org.openjdk.jmh.runner.TestMicro.dummyWarm, xxx, AverageTime, 1=
+org.openjdk.jmh.runner.TestMicro.dummyWarmOnly, xxx, AverageTime, 1=
+org.openjdk.jmh.runner.TestMicro.dummySetupPayload, xxx, AverageTime, 1=
+org.openjdk.jmh.runner.TestMicro.boom_Exception, xxx, AverageTime, 1=
+org.openjdk.jmh.runner.TestMicro.boom_Error, xxx, AverageTime, 1=
+org.openjdk.jmh.runner.TestMicro.boom_Throwable, xxx, AverageTime, 1=
+org.openjdk.jmh.runner.TestMicro.blackholed, xxx, AverageTime, 1=
+org.openjdk.jmh.runner.TestBrokenMicro.dummyPayload, xxx, AverageTime, 1=
+org.openjdk.jmh.runner.TestExceptionThrowingMicro.ouch, xxx, AverageTime, 1=
\ No newline at end of file
--- a/jmh-samples/src/main/java/org/openjdk/jmh/samples/JMHSample_15_Asymmetric.java	Tue Nov 19 15:07:36 2013 +0400
+++ b/jmh-samples/src/main/java/org/openjdk/jmh/samples/JMHSample_15_Asymmetric.java	Tue Nov 19 21:11:26 2013 +0400
@@ -27,12 +27,12 @@
 import org.openjdk.jmh.annotations.BenchmarkMode;
 import org.openjdk.jmh.annotations.GenerateMicroBenchmark;
 import org.openjdk.jmh.annotations.Group;
+import org.openjdk.jmh.annotations.GroupThreads;
 import org.openjdk.jmh.annotations.Mode;
 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.Threads;
 
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -48,18 +48,19 @@
      * which can bind several methods together, and all the threads are distributed among
      * the test methods.
      *
-     * Each execution group consists of two or more threads. If more threads are requested
+     * Each execution group contains of two or more threads. If more threads are requested
      * to be run via the command line option, then more execution groups are created.
-     * The meaning of @Threads slightly changes with this mode: now, it marks how many threads
-     * are executing the method in the single execution group.
+     * JMH will always round up the actual thread count to the execution group size, this
+     * will only allow the full execution groups.
      *
      * Note that two state scopes: Scope.Benchmark and Scope.Thread are not covering all
      * the use cases here -- you either share everything in the state, or share nothing.
      * To break this, we have the middle ground Scope.Group, which marks the state to be
      * shared within the execution group, but not among the groups.
      *
-     * Putting this all together, if we want to run this with 8 threads, this example means:
-     *  a) make two execution groups, each having 4 threads
+     * Putting this all together, if we want to run this with 8 threads, the example below
+     * means:
+     *  a) make two execution groups, each having 4 threads (by default)
      *  b) each execution group has one benchmark instance to share (= share the $counter)
      *  c) each execution group has 3 thread executing inc().
      *  d) each execution group has 1 thread executing get().
@@ -74,13 +75,14 @@
 
     @GenerateMicroBenchmark
     @Group("g")
-    @Threads(3)
+    @GroupThreads(3)
     public int inc() {
         return counter.incrementAndGet();
     }
 
     @GenerateMicroBenchmark
     @Group("g")
+    @GroupThreads(1)
     public int get() {
         return counter.get();
     }