Open In App

Java Microbenchmark Harness (JMH)

The JMH Java harness is used to create, execute, and analyze benchmarks in the nano, micro, milli, and macro domains that are written in Java and other JVM-targeting languages. A benchmark is a measurement or reference point that may be used to compare or evaluate something. Programming terminology for benchmarking is "performance code segment comparison." Thus, the overall approach of micro-benchmarking is even more concentrated on measuring the performance of tiny code units of an application.

Example of Java Microbenchmark Harness (JMH)

import org.openjdk.jmh.annotations.*;

import java.util.concurrent.TimeUnit;

/**
 * A simple JMH benchmark to compare the performance of summing an array using a loop vs. using streams.
 */
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@State(Scope.Thread)
public class SimpleBenchmark {

    private static final int ARRAY_SIZE = 1_000_000;
    private int[] data;

    /**
     * Setup method to initialize data for the benchmark.
     */
    @Setup(Level.Trial)
    public void setup() {
        data = new int[ARRAY_SIZE];
        for (int i = 0; i < ARRAY_SIZE; i++) {
            data[i] = i;
        }
    }

    /**
     * Benchmark method to sum the array using a loop.
     *
     * @return The sum of the array elements
     */
    @Benchmark
    public long sumUsingLoop() {
        long sum = 0;
        for (int value : data) {
            sum += value;
        }
        return sum;
    }

    /**
     * Benchmark method to sum the array using streams.
     *
     * @return The sum of the array elements
     */
    @Benchmark
    public long sumUsingStreams() {
        return java.util.Arrays.stream(data).sum();
    }

    /**
     * Main method to run the benchmark.
     *
     * @param args Command line arguments
     * @throws Exception If an error occurs during benchmark execution
     */
    public static void main(String[] args) throws Exception {
        org.openjdk.jmh.Main.main(args);
    }
}

Key features and Components of Java Microbenchmark Harness

Implementation of Java Microbenchmark Harness (JMH)

Step 1: Add Maven Dependencies

Simply defining the requirements will get us started, and we can really continue using Java 8:

<dependency>
    <groupId>org.openjdk.jmh</groupId>
    <artifactId>jmh-core</artifactId>
    <version>1.37</version>
</dependency>
<dependency>
    <groupId>org.openjdk.jmh</groupId>
    <artifactId>jmh-generator-annprocess</artifactId>
    <version>1.37</version>
</dependency>

Step 2: Execute all benchmarks

First, we must launch the org.openjdk.jmh process in order to run all benchmarks. The function Main.main().

import org.openjdk.jmh.annotations.*;

@State(Scope.Benchmark)
// This class represents a stateful object for benchmarking.
public class InitializationBenchmark {

    @Benchmark
    // @Benchmark annotation marks the method as a benchmark to be executed by JMH.
    @BenchmarkMode(Mode.AverageTime)
    // @BenchmarkMode specifies the benchmark mode as AverageTime, measuring the average time taken per operation.
    public void init() {
        // This benchmark method is used to measure the average time taken for an initialization operation.
        // In this case, the method does nothing (no operation is performed for the sake of benchmarking).
        // The goal is to measure the baseline performance of the benchmarking infrastructure.
        // You might replace the comment "// Do nothing" with the actual initialization code you want to benchmark.
    }

}

Output:

BenchMark Count


Step 3: Use a State object

Let's say we choose to hash a password several hundred times in order to provide additional defense against dictionary attacks on a password database. We may use a State object to investigate the performance impact:

import org.openjdk.jmh.annotations.*;

import com.google.common.hash.Hasher;
import com.google.common.hash.Hashing;

@State(Scope.Benchmark)
// This class represents the state for benchmarking purposes.
public class ExecutionPlan {

    @Param({ "10", "20", "30", "50", "100" })
    // This annotation specifies the parameters for the benchmark iterations.
    // The 'iterations' variable will take on the values 10, 20, 30, 50, and 100 during benchmarking.
    public int iterations;

    // This variable holds a Hasher instance for the Murmur3 hash function.
    public Hasher murmur3;

    // A sample password string for hashing.
    public String password = "5vnr5yui3yui2i";

    @Setup(Level.Invocation)
    // This setup method is executed before each benchmark invocation.
    public void setUp() {
        // Initialize the Murmur3 Hasher before each benchmark iteration.
        murmur3 = Hashing.murmur3_128().newHasher();
    }
}

Output:

BenchMark State

Step 4: Use dead code elimination

It's critical to understand optimizations while executing microbenchmarks. They might have a very deceptive effect on the benchmark findings. In order to provide some context, let's look at an example:

@Benchmark
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@BenchmarkMode(Mode.AverageTime)
public void doNothing() {
    // This benchmark method represents doing nothing.
    // It is used to measure the baseline performance overhead of the benchmarking infrastructure.
    // The annotations specify that the time unit is in nanoseconds and the benchmark mode is average time.
    // The method itself doesn't perform any meaningful computation, serving as a reference for minimal overhead.
}

@Benchmark
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@BenchmarkMode(Mode.AverageTime)
public void objectCreation() {
    // This benchmark method measures the time taken for creating a new Object.
    // It is useful for assessing the performance impact of object instantiation in the benchmarked environment.
    // The annotations specify that the time unit is in nanoseconds and the benchmark mode is average time.
    // The method creates a new Object, and the benchmarking infrastructure measures the time it takes.
}

Output:

BenchMark Time Units

Step 5: Encapsulate the constant state

We may enclose the constant state inside a state object to stop it from folding indefinitely:

@State(Scope.Benchmark)
public static class Log {
    // This class represents a stateful object for benchmarking.
    // It has a public field 'x' initialized to 10.
    public int x = 10;
}

@Benchmark
// This annotation marks the method as a benchmark to be executed by JMH.
public double log(Log input) {
    // This benchmark method calculates the natural logarithm (base e) of the 'x' field
    // in the provided Log object and returns the result.
    return Math.log(input.x);
}

Output:

BenchMark Log

Article Tags :