Performance benchmarking small functions in Java is notoriously difficult, and there are a number of tools out there to help (caliper, others). Those other tools require a fair amount of setup and installation to get them working.
This code is adapted from a question @skiwi asked. He produced this ProjectEuler framework, and I have adapted it to be more performance-monitoring friendly.
The basic premise is that you want to run a method multiple times. Some of those are warmup times, and the others are 'real' runs. The tool performs the warmup, then later does the real run. It averages the execution times for the real run to produce the performance time for the method.
The bulk of the logic is incorporated in to a class called Problem
which has an execute()
method that is abstract.
package euler;
public abstract class Problem<T> {
private final String name;
private final int warmup;
private final int realruns;
public Problem(String name, int warmups, int realruns) {
this.name = name;
this.warmup = warmups;
this.realruns = realruns;
}
public String getResult() {
return String.valueOf(execute());
}
public final int getWarmups() {
return warmup;
}
public final int getRealRuns() {
return realruns;
}
public final String getName() {
return name;
}
public abstract T execute();
}
A typical implementation of this Problem, for example, is to calculate the average of an array of integers, and it would be implemented as:
public class AverageIntegers extends Problem<Double>{
private static final int[] DATA = {1, 2, 3, 4, 5, 6, 7, 8, 9, 8, 7, 6, 5, 4, 3, 2, 1};
public AverageIntegers() {
super("Average Integers", 1000, 10000);
}
@Override
public Double execute() {
int sum = 0;
for (int v : DATA) {
sum += v;
}
return sum / (double)DATA.length;
}
}
With the above implementation, you can incorporate it in to the benchmark class:
package euler;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
public class ProjectEuler {
/**
* @param args
* the command line arguments
*/
public static void main(String[] args) {
ProjectEuler pe = new ProjectEuler();
pe.process();
System.out.println("\n\nWarmup Complete\n\n");
pe.process();
}
private static final int longestName(List<Problem<?>> probs) {
int namelen = 0;
for (Problem<?> p : probs) {
namelen = Math.max(namelen, p.getName().length());
}
return namelen;
}
private static final double MILLION = 1_000_000.0;
private final List<Problem<?>> problems = new ArrayList<>();
private final int longestname;
public ProjectEuler() {
/* **********************************
* ADD YOUR PROBLEMS HERE!
* ***********************************/
problems.add(new AverageIntegers());
// problems.add(new AlternativeImplementation1());
// problems.add(new AlternativeImplementation2());
// ....
longestname = longestName(problems);
}
private void process() {
problems.stream().forEachOrdered(new ProblemConsumer());
}
private class ProblemConsumer implements Consumer<Problem<?>> {
@Override
public void accept(final Problem<?> problem) {
final long basetime = System.nanoTime();
final int wreps = problem.getWarmups();
final int rreps = problem.getRealRuns();
long btime = System.nanoTime();
final String result = problem.getResult();
btime = System.nanoTime() - btime;
for (int i = wreps; i > 0; i--) {
String actual = problem.getResult();
if (!result.equals(actual)) {
throw new IllegalStateException("Unexpected result "
+ actual);
}
;
}
System.gc();
final long start = System.nanoTime();
for (int i = rreps; i > 0; i--) {
problem.execute();
}
final long end = System.nanoTime();
final long elapsed = end - start;
String actual = problem.getResult();
System.out.printf("%-" + longestname
+ "s => %s (hot %.5fms - cold %.3fms (total %.3fms))\n",
problem.getName(), actual, (elapsed / MILLION) / rreps,
btime / MILLION, (end - basetime) / MILLION);
}
}
}
warmups
the other two calls toprocess()
with aprintln("\n\nWarmup Complete\n\n")
in between? \$\endgroup\$