/** * Copyright (C) 2009 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.caliper; import static com.google.common.base.Preconditions.checkArgument; /** * Measure's the benchmark's per-trial execution time. */ class Caliper { private final long warmupNanos; private final long runNanos; Caliper(long warmupMillis, long runMillis) { checkArgument(warmupMillis > 50); checkArgument(runMillis > 50); this.warmupNanos = warmupMillis * 1000000; this.runNanos = runMillis * 1000000; } public double warmUp(TimedRunnable timedRunnable) throws Exception { long startNanos = System.nanoTime(); long endNanos = startNanos + warmupNanos; long currentNanos; int netReps = 0; int reps = 1; /* * Run progressively more reps at a time until we cross our warmup * threshold. This way any just-in-time compiler will be comfortable running * multiple iterations of our measurement method. */ while ((currentNanos = System.nanoTime()) < endNanos) { timedRunnable.run(reps); netReps += reps; reps *= 2; } double nanosPerExecution = (currentNanos - startNanos) / (double) netReps; if (nanosPerExecution > 1000000000 || nanosPerExecution < 2) { throw new ConfigurationException("Runtime " + nanosPerExecution + " out of range"); } return nanosPerExecution; } /** * In the run proper, we predict how extrapolate based on warmup how many * runs we're going to need, and run them all in a single batch. */ public double run(TimedRunnable test, double estimatedNanosPerTrial) throws Exception { @SuppressWarnings("NumericCastThatLosesPrecision") int trials = (int) (runNanos / estimatedNanosPerTrial); if (trials == 0) { trials = 1; } double nanosPerTrial = measure(test, trials); // if the runtime was in the expected range, return it. We're good. if (isPlausible(estimatedNanosPerTrial, nanosPerTrial)) { return nanosPerTrial; } // The runtime was outside of the expected range. Perhaps the VM is inlining // things too aggressively? We'll run more rounds to confirm that the // runtime scales with the number of trials. double nanosPerTrial2 = measure(test, trials * 4); if (isPlausible(nanosPerTrial, nanosPerTrial2)) { return nanosPerTrial; } throw new ConfigurationException("Measurement error: " + "runtime isn't proportional to the number of repetitions!"); } /** * Returns true if the given measurement is consistent with the expected * measurement. */ private boolean isPlausible(double expected, double measurement) { double ratio = measurement / expected; return ratio > 0.5 && ratio < 2.0; } private double measure(TimedRunnable test, int trials) throws Exception { prepareForTest(); long startNanos = System.nanoTime(); test.run(trials); return (System.nanoTime() - startNanos) / (double) trials; } private void prepareForTest() { System.gc(); System.gc(); } }