/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to you 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 org.eigenbase.util;
import java.text.MessageFormat;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.hydromatic.linq4j.function.Function1;
/**
* Helps to run benchmarks by running the same task repeatedly and averaging
* the running times.
*/
public class Benchmark {
/**
* Certain tests are enabled only if logging is enabled at debug level or
* higher.
*/
public static final Logger LOGGER =
Logger.getLogger(Benchmark.class.getCanonicalName());
private final Function1<Statistician, Void> function;
private final int repeat;
private final Statistician statistician;
public Benchmark(String description, Function1<Statistician, Void> function,
int repeat) {
this.function = function;
this.repeat = repeat;
this.statistician = new Statistician(description);
}
/**
* Returns whether performance tests are enabled.
*/
public static boolean enabled() {
return LOGGER.isLoggable(Level.FINE);
}
static long printDuration(String desc, long t0) {
final long t1 = System.nanoTime();
final long duration = t1 - t0;
LOGGER.finer(desc + " took " + duration + " nanos");
return duration;
}
public void run() {
for (int i = 0; i < repeat; i++) {
function.apply(statistician);
}
statistician.printDurations();
}
/**
* Collects statistics for a test that is run multiple times.
*/
public static class Statistician {
private final String desc;
private final List<Long> durations = new ArrayList<Long>();
public Statistician(String desc) {
super();
this.desc = desc;
}
public void record(long start) {
durations.add(
printDuration(
desc + " iteration #" + (durations.size() + 1), start));
}
private void printDurations() {
if (!LOGGER.isLoggable(Level.FINE)) {
return;
}
List<Long> coreDurations = durations;
String durationsString = durations.toString(); // save before sort
// Ignore the first 3 readings. (JIT compilation takes a while to
// kick in.)
if (coreDurations.size() > 3) {
coreDurations = durations.subList(3, durations.size());
}
Collections.sort(coreDurations);
// Further ignore the max and min.
List<Long> coreCoreDurations = coreDurations;
if (coreDurations.size() > 4) {
coreCoreDurations =
coreDurations.subList(1, coreDurations.size() - 1);
}
long sum = 0;
int count = coreCoreDurations.size();
for (long duration : coreCoreDurations) {
sum += duration;
}
final double avg = ((double) sum) / count;
double y = 0;
for (long duration : coreCoreDurations) {
double x = duration - avg;
y += x * x;
}
final double stddev = Math.sqrt(y / count);
if (durations.size() == 0) {
LOGGER.fine(MessageFormat.format("{0}: {1}", desc, "no runs"));
} else {
LOGGER.fine(
MessageFormat.format(
"{0}: {1} first; {2} +- {3}; {4} min; {5} max; {6} nanos",
desc,
durations.get(0), avg, stddev, coreDurations.get(0),
Util.last(coreDurations), durationsString));
}
}
}
}
// End Benchmark.java