Skip to main content
Became Hot Network Question
added 191 characters in body
Source Link
coderodde
  • 32.3k
  • 15
  • 79
  • 205

(The next version is here.)

(The next version is here.)

Source Link
coderodde
  • 32.3k
  • 15
  • 79
  • 205

A tiny Java framework for gathering running time statistics - Take II

Intro

(The previous version is here.)

Now I have incorporated nice answers made by Chris and Chip01.

What changed:

  1. Fixed the computation of median,
  2. RunStatistics is 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.