diff --git a/tests/org.eclipse.ui.tests.performance/src/org/eclipse/ui/tests/performance/CommandsPerformanceTest.java b/tests/org.eclipse.ui.tests.performance/src/org/eclipse/ui/tests/performance/CommandsPerformanceTest.java
index cc32eb7bb7e..86be95903f7 100644
--- a/tests/org.eclipse.ui.tests.performance/src/org/eclipse/ui/tests/performance/CommandsPerformanceTest.java
+++ b/tests/org.eclipse.ui.tests.performance/src/org/eclipse/ui/tests/performance/CommandsPerformanceTest.java
@@ -13,11 +13,18 @@
*******************************************************************************/
package org.eclipse.ui.tests.performance;
+import static org.eclipse.ui.tests.performance.UIPerformanceTestUtil.exercise;
+import static org.eclipse.ui.tests.performance.UIPerformanceTestUtil.reportTimings;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
+import java.util.Map;
import java.util.Set;
+import java.util.function.IntSupplier;
import org.eclipse.core.commands.Command;
import org.eclipse.core.commands.CommandManager;
@@ -25,6 +32,7 @@
import org.eclipse.core.commands.common.NotDefinedException;
import org.eclipse.core.commands.contexts.Context;
import org.eclipse.core.commands.contexts.ContextManager;
+import org.eclipse.core.runtime.CoreException;
import org.eclipse.jface.bindings.Binding;
import org.eclipse.jface.bindings.BindingManager;
import org.eclipse.jface.bindings.Scheme;
@@ -33,9 +41,7 @@
import org.eclipse.jface.bindings.keys.KeyLookupFactory;
import org.eclipse.jface.bindings.keys.KeySequence;
import org.eclipse.jface.bindings.keys.KeyStroke;
-import org.eclipse.jface.bindings.keys.ParseException;
import org.eclipse.jface.util.Util;
-import org.eclipse.test.performance.PerformanceTestCaseJunit4;
import org.eclipse.ui.tests.harness.util.CloseTestWindowsRule;
import org.junit.After;
import org.junit.Before;
@@ -53,7 +59,34 @@
*
* @since 3.1
*/
-public final class CommandsPerformanceTest extends PerformanceTestCaseJunit4 {
+public final class CommandsPerformanceTest {
+
+ /**
+ * A single cached look-up is far too fast to time individually, so the hit
+ * tests time a batch of them and report the batch.
+ */
+ private static final int HARD_HIT_BATCH = 100000;
+
+ private static final int SOFT_HIT_BATCH = 1000;
+
+ private static final int WARMUP_ROUNDS = 5;
+
+ private static final int MIN_ROUNDS = 10;
+
+ private static final int MAX_ROUNDS = 200;
+
+ private static final int MAX_MEASURE_TIME_MS = 5000;
+
+ /** Rebuilding the binding set for every sample is what makes the miss real. */
+ private static final int MISS_WARMUP_ROUNDS = 3;
+
+ private static final int MISS_ROUNDS = 20;
+
+ private static final int REVERSE_LOOKUP_COMMANDS = 64;
+
+ /** Keeps the measured look-ups from being optimized away. */
+ @SuppressWarnings("unused")
+ private static volatile long sink;
@ClassRule
public static final UIPerformanceTestRule uiPerformanceTestRule = new UIPerformanceTestRule();
@@ -158,6 +191,12 @@ private static final void createScheme(final BindingManager bindingManager,
*/
private ContextManager contextManager = null;
+ /**
+ * Commands the reverse look-up cycles through. Looking up a different command
+ * each time keeps the call from being hoisted out of the measured loop.
+ */
+ private final List boundCommands = new ArrayList<>();
+
/**
*
* Sets up a sufficiently complex set of bindings.
@@ -186,6 +225,15 @@ private static final void createScheme(final BindingManager bindingManager,
*/
@Before
public final void setUpBindings() throws NotDefinedException, Exception {
+ buildBindings();
+ }
+
+ /**
+ * Builds a fresh command, context and binding set. Calling this again discards
+ * the previous binding manager together with its look-up cache.
+ */
+ private void buildBindings() throws NotDefinedException, Exception {
+ boundCommands.clear();
/*
* The constants to use in creating the various objects. The platform
* locale count must be greater than or equal to the number of deletion
@@ -348,6 +396,9 @@ public final void setUpBindings() throws NotDefinedException, Exception {
parameterizedCommand, schemeId, contextId, locale,
platform, null, type);
bindings[i + deletionMarkers] = binding;
+ if (boundCommands.size() < REVERSE_LOOKUP_COMMANDS) {
+ boundCommands.add(parameterizedCommand);
+ }
}
bindingManager.setBindings(bindings);
}
@@ -365,27 +416,14 @@ public final void clearBindings() throws Exception {
* changed. It measures how long it takes to look up the computation from
* the cache one million times.
*
- *
- * @throws ParseException
- * If "CTRL+F" can't be parsed for some strange reason.
*/
@Test
- public final void testBindingCacheHitHard() throws ParseException {
- // Constants
- final int cacheHits = 1000000;
+ public final void testBindingCacheHitHard() throws Exception {
final KeySequence keySequence = KeySequence.getInstance("CTRL+F");
+ assertNotNull("No partial matches to look up", bindingManager.getPartialMatches(keySequence));
- // Compute once.
- bindingManager.getPartialMatches(keySequence);
-
- // Time how long it takes to access the cache;
- startMeasuring();
- for (int i = 0; i < cacheHits; i++) {
- bindingManager.getPartialMatches(keySequence);
- }
- stopMeasuring();
- commitMeasurements();
- assertPerformance();
+ measureBatched("BindingCache hit hard", HARD_HIT_BATCH,
+ () -> bindingManager.getPartialMatches(keySequence).size());
}
/**
@@ -395,27 +433,17 @@ public final void testBindingCacheHitHard() throws ParseException {
* the cache one million times. In this test, the look-up is done in reverse --
* from command identifier to trigger.
*
- *
- * @throws ParseException
- * If "CTRL+F" can't be parsed for some strange reason.
*/
@Test
- public final void testBindingCacheHitHardReverse() throws ParseException {
- // Constants
- final int cacheHits = 1000000;
+ public final void testBindingCacheHitHardReverse() throws Exception {
final KeySequence keySequence = KeySequence.getInstance("CTRL+F");
-
- // Compute once.
bindingManager.getPartialMatches(keySequence);
+ assertFalse("No bound commands to look up", boundCommands.isEmpty());
+ assertNotNull("No reverse look-up result", bindingManager.getActiveBindingsFor(boundCommands.get(0)));
- // Time how long it takes to access the cache;
- startMeasuring();
- for (int i = 0; i < cacheHits; i++) {
- bindingManager.getActiveBindingsFor((ParameterizedCommand) null);
- }
- stopMeasuring();
- commitMeasurements();
- assertPerformance();
+ final int[] next = { 0 };
+ measureBatched("BindingCache hit hard reverse", HARD_HIT_BATCH, () -> bindingManager
+ .getActiveBindingsFor(boundCommands.get(next[0]++ % boundCommands.size())).length);
}
/**
@@ -424,38 +452,25 @@ public final void testBindingCacheHitHardReverse() throws ParseException {
* changed, but the cache contains a matching entry. It measures how long it
* takes to look up the computation from the cache forty thousand times.
*
- *
- * @throws ParseException
- * If "CTRL+F" can't be parsed for some strange reason.
*/
@Test
- public final void testBindingCacheHitSoft() throws ParseException {
- // Constants
- final int cacheHits = 10000;
+ public final void testBindingCacheHitSoft() throws Exception {
final KeySequence keySequence = KeySequence.getInstance("CTRL+F");
- // Compute once for each context set.
+ // Compute once for each context set, so both are in the cache.
final Set> contextSet1 = contextManager.getActiveContextIds();
bindingManager.getPartialMatches(keySequence);
final List> contextList = new ArrayList<>(contextSet1);
contextList.remove(contextList.size() - 1);
final Set> contextSet2 = new HashSet<>(contextList);
contextManager.setActiveContextIds(contextSet2);
- bindingManager.getPartialMatches(keySequence);
+ assertNotNull("No partial matches to look up", bindingManager.getPartialMatches(keySequence));
- // Time how long it takes to access the cache;
- startMeasuring();
- for (int i = 0; i < cacheHits; i++) {
- if ((i % 2) == 0) {
- contextManager.setActiveContextIds(contextSet1);
- } else {
- contextManager.setActiveContextIds(contextSet2);
- }
- bindingManager.getPartialMatches(keySequence);
- }
- stopMeasuring();
- commitMeasurements();
- assertPerformance();
+ final int[] alternating = { 0 };
+ measureBatched("BindingCache hit soft", SOFT_HIT_BATCH, () -> {
+ contextManager.setActiveContextIds(alternating[0]++ % 2 == 0 ? contextSet1 : contextSet2);
+ return bindingManager.getPartialMatches(keySequence).size();
+ });
}
/**
@@ -464,20 +479,52 @@ public final void testBindingCacheHitSoft() throws ParseException {
* an exceptionally large set of bindings. The binding set tries to mimick
* some of the same properties of a "real" binding set.
*
- *
- * @throws ParseException
- * If "CTRL+F" can't be parsed for some strange reason.
*/
@Test
- public final void testBindingCacheMissLarge() throws ParseException {
- // Constants
+ public final void testBindingCacheMissLarge() throws Exception {
final KeySequence keySequence = KeySequence.getInstance("CTRL+F");
- // Time how long it takes to solve the binding set.
- startMeasuring();
- bindingManager.getPartialMatches(keySequence);
- stopMeasuring();
- commitMeasurements();
- assertPerformance();
+ for (int i = 0; i < MISS_WARMUP_ROUNDS; i++) {
+ buildBindings();
+ bindingManager.getPartialMatches(keySequence);
+ }
+
+ List times = new ArrayList<>();
+ for (int i = 0; i < MISS_ROUNDS; i++) {
+ buildBindings();
+ long before = System.nanoTime();
+ Map, ?> matches = bindingManager.getPartialMatches(keySequence);
+ times.add(System.nanoTime() - before);
+ assertNotNull("No partial matches computed", matches);
+ }
+
+ reportTimings("BindingCache miss large", times);
+ }
+
+ /**
+ * Times the given look-up in batches of the given size and reports the
+ * distribution over the batches. Warms up first so that class loading and JIT
+ * stay out of the reported times.
+ */
+ private static void measureBatched(String label, int batchSize, IntSupplier lookup) throws CoreException {
+ for (int i = 0; i < WARMUP_ROUNDS; i++) {
+ runBatch(batchSize, lookup);
+ }
+
+ List times = new ArrayList<>();
+ exercise(() -> times.add(runBatch(batchSize, lookup)), MIN_ROUNDS, MAX_ROUNDS, MAX_MEASURE_TIME_MS);
+
+ reportTimings(label + " [per " + batchSize + " look-ups]", times);
+ }
+
+ private static long runBatch(int batchSize, IntSupplier lookup) {
+ long before = System.nanoTime();
+ long accumulated = 0;
+ for (int i = 0; i < batchSize; i++) {
+ accumulated += lookup.getAsInt();
+ }
+ long elapsed = System.nanoTime() - before;
+ sink += accumulated;
+ return elapsed;
}
}