Java Networking. This article describes how to write Java performance benchmarks with the JMH (Java Microbenchmark Harness) framework.

1. Using JMH

JMH is provided by set of libraries. To use it in your project you have to add the set of libraries to your project. For example with Maven you can use folowing additional dependencies.

    <dependencies>
        <dependency>
            <groupId>org.openjdk.jmh</groupId>
            <artifactId>jmh-core</artifactId>
            <version>1.33</version>
        </dependency>

        <dependency>
            <groupId>org.openjdk.jmh</groupId>
            <artifactId>jmh-generator-annprocess</artifactId>
            <version>1.33</version>
        </dependency>
    </dependencies>

After this can you write your tests using the @Benchmark annotation.

For example the following two:

package benchmarks;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.concurrent.TimeUnit;

import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Param;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Warmup;

@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 200, time = 1, timeUnit = TimeUnit.MILLISECONDS)
//@Fork(value = 3, jvmArgsAppend = {"-XX:+UseParallelGC", "-Xms1g", "-Xmx1g"})
@Fork(value = 1)
@BenchmarkMode(org.openjdk.jmh.annotations.Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(org.openjdk.jmh.annotations.Scope.Benchmark)
public class ToArrayBench {

    @Param({ "0", "1", "10", "100", "1000" })
    int size;
    @Param({ "arraylist" })//, "hashset"
    String type;
    Collection<Foo> coll;

    @Setup
    public void setup() {
        if (type.equals("arraylist")) {
            coll = new ArrayList<Foo>();
        } else if (type.equals("hashset")) {
            coll = new HashSet<Foo>();
        } else {
            throw new IllegalStateException();
        }
        for (int i = 0; i < size; i++) {
            coll.add(new Foo(i));
        }
    }

    @Benchmark
    public Object[] simple() {
        return coll.toArray();
    }

    @Benchmark
    public Foo[] zero() {
        return coll.toArray(new Foo[0]);
    }

    @Benchmark
    public Foo[] sized() {
        return coll.toArray(new Foo[coll.size()]);
    }

    @Benchmark
    public Foo[] arrayNew() {
        return coll.toArray(Foo[]::new);
    }

    public static class Foo {
        private int i;

        public Foo(int i) {
            this.i = i;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o)
                return true;
            if (o == null || getClass() != o.getClass())
                return false;
            Foo foo = (Foo) o;
            return i == foo.i;
        }

        @Override
        public int hashCode() {
            return i;
        }
    }

}
package benchmarks;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.concurrent.TimeUnit;

import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Param;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Warmup;

@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 200, time = 1, timeUnit = TimeUnit.MILLISECONDS)
//@Fork(value = 3, jvmArgsAppend = {"-XX:+UseParallelGC", "-Xms1g", "-Xmx1g"})
@Fork(value = 1)
@BenchmarkMode(org.openjdk.jmh.annotations.Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(org.openjdk.jmh.annotations.Scope.Benchmark)
public class LocalInitialization {

    @Setup
    public void setup() {
    }

    @Benchmark
    public void unnecessaryInitialization() {
        String s = "";
        s = "Hello";
    }

    @Benchmark
    public void directAssignment() {
        String s =  "Hello";
    }
}

Afterwards you can add this benchmarks to a benchmark runner.

package benchmarks;

import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;

public class BenchmarkRunner {

    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder().include(LocalInitialization.class.getSimpleName()).include(ToArrayBench.class.getSimpleName()).build();

        new Runner(opt).run();
    }
}

Build your project via mvn clean verify and run your BenchmarkRunner class via your IDE.

This should run the benchmarks can return the collected data.

Benchmark                                      (size)     (type)  Mode  Cnt     Score     Error  Units
LocalInitialization.directAssignment              N/A        N/A  avgt  200     0,394 ±   0,015  ns/op
LocalInitialization.unnecessaryInitialization     N/A        N/A  avgt  200     0,392 ±   0,012  ns/op
ToArrayBench.arrayNew                               0  arraylist  avgt  200     6,181 ±   0,522  ns/op
ToArrayBench.arrayNew                               1  arraylist  avgt  200    22,034 ±   2,614  ns/op
ToArrayBench.arrayNew                              10  arraylist  avgt  200    34,663 ±   7,253  ns/op
ToArrayBench.arrayNew                             100  arraylist  avgt  200   197,565 ±  20,510  ns/op
ToArrayBench.arrayNew                            1000  arraylist  avgt  200  1513,145 ± 218,155  ns/op
ToArrayBench.simple                                 0  arraylist  avgt  200     7,936 ±   1,204  ns/op
ToArrayBench.simple                                 1  arraylist  avgt  200    11,370 ±   1,380  ns/op
ToArrayBench.simple                                10  arraylist  avgt  200    14,236 ±   2,174  ns/op
ToArrayBench.simple                               100  arraylist  avgt  200    94,752 ±  89,931  ns/op
ToArrayBench.simple                              1000  arraylist  avgt  200   608,612 ±  72,024  ns/op
ToArrayBench.sized                                  0  arraylist  avgt  200     8,028 ±   4,179  ns/op
ToArrayBench.sized                                  1  arraylist  avgt  200    17,983 ±   1,108  ns/op
ToArrayBench.sized                                 10  arraylist  avgt  200    33,522 ±   9,677  ns/op
ToArrayBench.sized                                100  arraylist  avgt  200   172,629 ±  22,120  ns/op
ToArrayBench.sized                               1000  arraylist  avgt  200  1627,501 ± 126,101  ns/op
ToArrayBench.zero                                   0  arraylist  avgt  200     5,601 ±   0,577  ns/op
ToArrayBench.zero                                   1  arraylist  avgt  200    24,291 ±   7,608  ns/op
ToArrayBench.zero                                  10  arraylist  avgt  200    32,702 ±   6,036  ns/op
ToArrayBench.zero                                 100  arraylist  avgt  200   170,355 ±  20,991  ns/op
ToArrayBench.zero                                1000  arraylist  avgt  200  1489,356 ± 181,910  ns/op

2. Resources

You find the code examples in our Github tutorial example as Maven project.

If you need more assistance we offer Online Training and Onsite training as well as consulting