blob: 7d96229f7e7282112d60a859618f370de11ae56b [file] [log] [blame]
Paul Duffine2363012015-11-30 16:20:41 +00001/*
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
17package com.google.caliper.worker;
18
19import static java.util.concurrent.TimeUnit.NANOSECONDS;
20
21import com.google.caliper.model.Measurement;
22import com.google.caliper.model.Value;
23import com.google.caliper.runner.InvalidBenchmarkException;
24import com.google.caliper.runner.Running.Benchmark;
25import com.google.caliper.runner.Running.BenchmarkMethod;
26import com.google.caliper.util.ShortDuration;
27import com.google.caliper.util.Util;
28import com.google.common.annotations.VisibleForTesting;
29import com.google.common.base.Ticker;
30import com.google.common.collect.ImmutableSet;
31
32import java.lang.reflect.Method;
33import java.util.Map;
34import java.util.Random;
35
36import javax.inject.Inject;
37
38/**
39 * A {@link Worker} base class for micro and pico benchmarks.
40 */
41public 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}