blob: 7d96229f7e7282112d60a859618f370de11ae56b [file] [log] [blame]
/*
* Copyright (C) 2011 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.worker;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
import com.google.caliper.model.Measurement;
import com.google.caliper.model.Value;
import com.google.caliper.runner.InvalidBenchmarkException;
import com.google.caliper.runner.Running.Benchmark;
import com.google.caliper.runner.Running.BenchmarkMethod;
import com.google.caliper.util.ShortDuration;
import com.google.caliper.util.Util;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Ticker;
import com.google.common.collect.ImmutableSet;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Random;
import javax.inject.Inject;
/**
* A {@link Worker} base class for micro and pico benchmarks.
*/
public abstract class RuntimeWorker extends Worker {
@VisibleForTesting static final int INITIAL_REPS = 100;
protected final Random random;
protected final Ticker ticker;
protected final Options options;
private long totalReps;
private long totalNanos;
private long nextReps;
RuntimeWorker(Object benchmark,
Method method, Random random, Ticker ticker,
Map<String, String> workerOptions) {
super(benchmark, method);
this.random = random;
// TODO(gak): investigate whether or not we can use Stopwatch
this.ticker = ticker;
this.options = new Options(workerOptions);
}
@Override public void bootstrap() throws Exception {
totalReps = INITIAL_REPS;
totalNanos = invokeTimeMethod(INITIAL_REPS);
}
@Override public void preMeasure(boolean inWarmup) throws Exception {
nextReps = calculateTargetReps(totalReps, totalNanos, options.timingIntervalNanos,
random.nextGaussian());
if (options.gcBeforeEach && !inWarmup) {
Util.forceGc();
}
}
@Override public Iterable<Measurement> measure() throws Exception {
long nanos = invokeTimeMethod(nextReps);
Measurement measurement = new Measurement.Builder()
.description("runtime")
.value(Value.create(nanos, "ns"))
.weight(nextReps)
.build();
totalReps += nextReps;
totalNanos += nanos;
return ImmutableSet.of(measurement);
}
abstract long invokeTimeMethod(long reps) throws Exception;
/**
* Returns a random number of reps based on a normal distribution around the estimated number of
* reps for the timing interval. The distribution used has a standard deviation of one fifth of
* the estimated number of reps.
*/
@VisibleForTesting static long calculateTargetReps(long reps, long nanos, long targetNanos,
double gaussian) {
double targetReps = (((double) reps) / nanos) * targetNanos;
return Math.max(1L, Math.round((gaussian * (targetReps / 5)) + targetReps));
}
/**
* A {@link Worker} for micro benchmarks.
*/
public static final class Micro extends RuntimeWorker {
@Inject Micro(@Benchmark Object benchmark,
@BenchmarkMethod Method method, Random random, Ticker ticker,
@WorkerOptions Map<String, String> workerOptions) {
super(benchmark, method, random, ticker, workerOptions);
}
@Override long invokeTimeMethod(long reps) throws Exception {
int intReps = (int) reps;
if (reps != intReps) {
throw new InvalidBenchmarkException("%s.%s takes an int for reps, "
+ "but requires a greater number to fill the given timing interval (%s). "
+ "If this is expected (the benchmarked code is very fast), use a long parameter."
+ "Otherwise, check your benchmark for errors.",
benchmark.getClass(), benchmarkMethod.getName(),
ShortDuration.of(options.timingIntervalNanos, NANOSECONDS));
}
long before = ticker.read();
benchmarkMethod.invoke(benchmark, intReps);
return ticker.read() - before;
}
}
/**
* A {@link Worker} for pico benchmarks.
*/
public static final class Pico extends RuntimeWorker {
@Inject Pico(@Benchmark Object benchmark,
@BenchmarkMethod Method method, Random random, Ticker ticker,
@WorkerOptions Map<String, String> workerOptions) {
super(benchmark, method, random, ticker, workerOptions);
}
@Override long invokeTimeMethod(long reps) throws Exception {
long before = ticker.read();
benchmarkMethod.invoke(benchmark, reps);
return ticker.read() - before;
}
}
private static final class Options {
long timingIntervalNanos;
boolean gcBeforeEach;
Options(Map<String, String> optionMap) {
this.timingIntervalNanos = Long.parseLong(optionMap.get("timingIntervalNanos"));
this.gcBeforeEach = Boolean.parseBoolean(optionMap.get("gcBeforeEach"));
}
}
}