Intro
(The previous version is here.)
(The next version is here.)
Now I have incorporated nice answers made by Chris and Chip01.
What changed:
- Fixed the computation of median,
RunStatisticsis now a record.
Code
package io.github.coderodde.statistics.run;
import java.text.NumberFormat;
import java.util.Locale;
/**
* This record class encapsulates the run statistics.
*
* @author Rodion "rodde" Efremov
*/
public final record RunStatistics(long minimumDuration,
long maximumDuration,
double meanDuration,
double medianDuration,
double standardDeviation) {
@Override
public String toString() {
// NumberFormat.format() is not thread-safe, so instantiate and call
// here:
final NumberFormat nf = NumberFormat.getInstance(Locale.getDefault());
return new StringBuilder("min = ")
.append(nf.format(minimumDuration))
.append(" ns, max = ")
.append(nf.format(maximumDuration))
.append(" ns, mean = ")
.append(meanDuration)
.append(" ns, median = ")
.append(medianDuration)
.append(" ns, sd = ")
.append(standardDeviation)
.append(" ns")
.toString();
}
}
package io.github.coderodde.statistics.run;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
/**
* This class provides methods for obtaining the running time statistics.
*
* @author Rodion "rodde" Efremov
*/
public final class Runner {
private static final int MINIMUM_ITERATIONS = 1;
public static RunStatistics measure(final Runnable runnable,
final int iterations) {
Objects.requireNonNull(runnable, "The input runnable is null");
checkIterations(iterations);
long minimumDuration = Long.MAX_VALUE;
long maximumDuration = Long.MIN_VALUE;
long meanDuration = 0;
double medianDuration;
double standardDeviation;
final List<Long> durations = new ArrayList<>(iterations);
for (int iteration = 0;
iteration < iterations;
iteration++) {
final long duration = measure(runnable);
minimumDuration = Math.min(minimumDuration, duration);
maximumDuration = Math.max(maximumDuration, duration);
meanDuration += duration;
durations.add(duration);
}
meanDuration /= iterations;
medianDuration = computeMedianDuration(durations);
standardDeviation = computeStandardDeviation(durations, meanDuration);
return new RunStatistics(minimumDuration,
maximumDuration,
meanDuration,
medianDuration,
standardDeviation);
}
public static RunStatistics measure(final List<Runnable> runnables) {
Objects.requireNonNull(runnables, "The input runnables is null");
if (runnables.isEmpty()) {
throw new IllegalArgumentException("Nothing to measure");
}
long minimumDuration = Long.MAX_VALUE;
long maximumDuration = Long.MIN_VALUE;
long meanDuration = 0;
double medianDuration;
double standardDeviation;
final List<Long> durations = new ArrayList<>(runnables.size());
for (final Runnable runnable : runnables) {
final long duration = measure(runnable);
minimumDuration = Math.min(minimumDuration, duration);
maximumDuration = Math.max(maximumDuration, duration);
meanDuration += duration;
durations.add(duration);
}
meanDuration /= runnables.size();
medianDuration = computeMedianDuration(durations);
standardDeviation = computeStandardDeviation(durations, meanDuration);
return new RunStatistics(minimumDuration,
maximumDuration,
meanDuration,
medianDuration,
standardDeviation);
}
private static long measure(final Runnable runnable) {
final long ta = System.nanoTime();
runnable.run();
final long tb = System.nanoTime();
final long duration = tb - ta;
return duration;
}
private static double computeMedianDuration(final List<Long> durations) {
Collections.sort(durations);
if (durations.size() % 2 == 1) {
return durations.get(durations.size() / 2);
} else {
final int index1 = durations.size() / 2;
final int index2 = index1 - 1;
return (durations.get(index1) + durations.get(index2)) / 2.0;
}
}
private static double computeStandardDeviation(final List<Long> durations,
final long meanDuration) {
double sum = 0.0;
for (final Long duration : durations) {
sum += Math.pow(duration - meanDuration, 2.0);
}
return Math.sqrt(sum / durations.size());
}
private static void checkIterations(final int iterations) {
if (iterations < MINIMUM_ITERATIONS) {
final String exceptionMessage =
String.format("Number of iterations (%d) is too small. " +
"Must be at least %d.",
iterations,
MINIMUM_ITERATIONS);
throw new IllegalArgumentException(exceptionMessage);
}
}
}
package io.github.coderodde.statistics.run.demo;
import io.github.coderodde.statistics.run.RunStatistics;
import io.github.coderodde.statistics.run.Runner;
import java.util.Random;
public class Demo {
private static final Random RANDOM = new Random(13L);
public static void main(String[] args) {
final RunStatistics runStatistics = Runner.measure(() -> {
try {
Thread.sleep(RANDOM.nextInt(100));
} catch (InterruptedException ex) {}
}, 20);
System.out.println(runStatistics);
}
}
Typical output
min = 18,600 ns, max = 98,308,200 ns, mean = 4.873858E7 ns, median = 5.643875E7 ns, sd = 3.0280325961531524E7 ns
Critique request
Please, tell me anything that comes to mind.