1 /* 2 * Copyright (C) 2011 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.google.caliper.worker; 18 19 import static java.util.concurrent.TimeUnit.NANOSECONDS; 20 21 import com.google.caliper.model.Measurement; 22 import com.google.caliper.model.Value; 23 import com.google.caliper.runner.InvalidBenchmarkException; 24 import com.google.caliper.runner.Running.Benchmark; 25 import com.google.caliper.runner.Running.BenchmarkMethod; 26 import com.google.caliper.util.ShortDuration; 27 import com.google.caliper.util.Util; 28 import com.google.common.annotations.VisibleForTesting; 29 import com.google.common.base.Ticker; 30 import com.google.common.collect.ImmutableSet; 31 32 import java.lang.reflect.Method; 33 import java.util.Map; 34 import java.util.Random; 35 36 import javax.inject.Inject; 37 38 /** 39 * A {@link Worker} base class for micro and pico benchmarks. 40 */ 41 public abstract class RuntimeWorker extends Worker { 42 @VisibleForTesting static final int INITIAL_REPS = 100; 43 44 protected final Random random; 45 protected final Ticker ticker; 46 protected final Options options; 47 private long totalReps; 48 private long totalNanos; 49 private long nextReps; 50 51 RuntimeWorker(Object benchmark, 52 Method method, Random random, Ticker ticker, 53 Map<String, String> workerOptions) { 54 super(benchmark, method); 55 this.random = random; 56 // TODO(gak): investigate whether or not we can use Stopwatch 57 this.ticker = ticker; 58 this.options = new Options(workerOptions); 59 } 60 61 @Override public void bootstrap() throws Exception { 62 totalReps = INITIAL_REPS; 63 totalNanos = invokeTimeMethod(INITIAL_REPS); 64 } 65 66 @Override public void preMeasure(boolean inWarmup) throws Exception { 67 nextReps = calculateTargetReps(totalReps, totalNanos, options.timingIntervalNanos, 68 random.nextGaussian()); 69 if (options.gcBeforeEach && !inWarmup) { 70 Util.forceGc(); 71 } 72 } 73 74 @Override public Iterable<Measurement> measure() throws Exception { 75 long nanos = invokeTimeMethod(nextReps); 76 Measurement measurement = new Measurement.Builder() 77 .description("runtime") 78 .value(Value.create(nanos, "ns")) 79 .weight(nextReps) 80 .build(); 81 82 totalReps += nextReps; 83 totalNanos += nanos; 84 return ImmutableSet.of(measurement); 85 } 86 87 abstract long invokeTimeMethod(long reps) throws Exception; 88 89 /** 90 * Returns a random number of reps based on a normal distribution around the estimated number of 91 * reps for the timing interval. The distribution used has a standard deviation of one fifth of 92 * the estimated number of reps. 93 */ 94 @VisibleForTesting static long calculateTargetReps(long reps, long nanos, long targetNanos, 95 double gaussian) { 96 double targetReps = (((double) reps) / nanos) * targetNanos; 97 return Math.max(1L, Math.round((gaussian * (targetReps / 5)) + targetReps)); 98 } 99 100 /** 101 * A {@link Worker} for micro benchmarks. 102 */ 103 public static final class Micro extends RuntimeWorker { 104 @Inject Micro(@Benchmark Object benchmark, 105 @BenchmarkMethod Method method, Random random, Ticker ticker, 106 @WorkerOptions Map<String, String> workerOptions) { 107 super(benchmark, method, random, ticker, workerOptions); 108 } 109 110 @Override long invokeTimeMethod(long reps) throws Exception { 111 int intReps = (int) reps; 112 if (reps != intReps) { 113 throw new InvalidBenchmarkException("%s.%s takes an int for reps, " 114 + "but requires a greater number to fill the given timing interval (%s). " 115 + "If this is expected (the benchmarked code is very fast), use a long parameter." 116 + "Otherwise, check your benchmark for errors.", 117 benchmark.getClass(), benchmarkMethod.getName(), 118 ShortDuration.of(options.timingIntervalNanos, NANOSECONDS)); 119 } 120 long before = ticker.read(); 121 benchmarkMethod.invoke(benchmark, intReps); 122 return ticker.read() - before; 123 } 124 } 125 126 /** 127 * A {@link Worker} for pico benchmarks. 128 */ 129 public static final class Pico extends RuntimeWorker { 130 @Inject Pico(@Benchmark Object benchmark, 131 @BenchmarkMethod Method method, Random random, Ticker ticker, 132 @WorkerOptions Map<String, String> workerOptions) { 133 super(benchmark, method, random, ticker, workerOptions); 134 } 135 136 @Override long invokeTimeMethod(long reps) throws Exception { 137 long before = ticker.read(); 138 benchmarkMethod.invoke(benchmark, reps); 139 return ticker.read() - before; 140 } 141 } 142 143 private static final class Options { 144 long timingIntervalNanos; 145 boolean gcBeforeEach; 146 147 Options(Map<String, String> optionMap) { 148 this.timingIntervalNanos = Long.parseLong(optionMap.get("timingIntervalNanos")); 149 this.gcBeforeEach = Boolean.parseBoolean(optionMap.get("gcBeforeEach")); 150 } 151 } 152 } 153