Skip to content

jmh - microbenchmark framework

jmh は microbenchmark framework です。

ref. http://openjdk.java.net/projects/code-tools/jmh/

jmh-corejmh-generator-anonprocess を依存に入れます。

groovy
dependencies {
  implementation 'org.openjdk.jmh:jmh-core:1.14.1'
  implementation 'org.openjdk.jmh:jmh-generator-annprocess:1.14.1'
}

あとは、ベンチマークを書きます。@Benchmark をつけるだけで使えます。

annotation processor を利用しているので、IntelliJ から起動する場合には Annotation Processor を有効にしてください。 Build, Execution, Deployment > Compiler > Annotation Processors から Enable Annotation Processors に チェックする必要があります。

java
package com.example;

import org.openjdk.jmh.annotations.Benchmark;
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;

import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.util.stream.IntStream;

public class ListBenchmark {
    @Benchmark
    public int streamSum() throws URISyntaxException {
        return IntStream.range(0, 1000)
                .sum();
    }

    @Benchmark
    public int forSum() throws MalformedURLException {
        int sum = 0;
        for (int i = 0; i < 1000; i++) {
            sum += i;
        }
        return sum;
    }

    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
                .include(ListBenchmark.class.getSimpleName())
                .warmupIterations(5)
                .measurementIterations(5)
                .forks(1)
                .build();

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

この main method から起動すると以下のような結果が出力されます。

/Library/Java/JavaVirtualMachines/jdk1.8.0_77.jdk/Contents/Home/bin/java -Didea.launcher.port=7536 "-Didea.launcher.bin.path=/Applications/IntelliJ IDEA.app/Contents/bin" -Dfile.encoding=UTF-8 -classpath "/Library/Java/JavaVirtualMachines/jdk1.8.0_77.jdk/Contents/Home/jre/lib/charsets.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_77.jdk/Contents/Home/jre/lib/deploy.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_77.jdk/Contents/Home/jre/lib/ext/cldrdata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_77.jdk/Contents/Home/jre/lib/ext/dnsns.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_77.jdk/Contents/Home/jre/lib/ext/jaccess.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_77.jdk/Contents/Home/jre/lib/ext/jfxrt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_77.jdk/Contents/Home/jre/lib/ext/localedata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_77.jdk/Contents/Home/jre/lib/ext/nashorn.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_77.jdk/Contents/Home/jre/lib/ext/sunec.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_77.jdk/Contents/Home/jre/lib/ext/sunjce_provider.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_77.jdk/Contents/Home/jre/lib/ext/sunpkcs11.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_77.jdk/Contents/Home/jre/lib/ext/zipfs.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_77.jdk/Contents/Home/jre/lib/javaws.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_77.jdk/Contents/Home/jre/lib/jce.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_77.jdk/Contents/Home/jre/lib/jfr.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_77.jdk/Contents/Home/jre/lib/jfxswt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_77.jdk/Contents/Home/jre/lib/jsse.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_77.jdk/Contents/Home/jre/lib/management-agent.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_77.jdk/Contents/Home/jre/lib/plugin.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_77.jdk/Contents/Home/jre/lib/resources.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_77.jdk/Contents/Home/jre/lib/rt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_77.jdk/Contents/Home/lib/ant-javafx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_77.jdk/Contents/Home/lib/dt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_77.jdk/Contents/Home/lib/javafx-mx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_77.jdk/Contents/Home/lib/jconsole.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_77.jdk/Contents/Home/lib/packager.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_77.jdk/Contents/Home/lib/sa-jdi.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_77.jdk/Contents/Home/lib/tools.jar:/Users/tokuhirom/dev/java-handbook/samples/jmh/build/classes/main:/Users/tokuhirom/.gradle/caches/modules-2/files-2.1/org.openjdk.jmh/jmh-core/1.14.1/5d6686fd71204d467e10d306d8c356a14a80770f/jmh-core-1.14.1.jar:/Users/tokuhirom/.gradle/caches/modules-2/files-2.1/org.openjdk.jmh/jmh-generator-annprocess/1.14.1/830dc27f70d7036b405e3f024cb4c2fa822bca72/jmh-generator-annprocess-1.14.1.jar:/Users/tokuhirom/.gradle/caches/modules-2/files-2.1/net.sf.jopt-simple/jopt-simple/5.0.2/98cafc6081d5632b61be2c9e60650b64ddbc637c/jopt-simple-5.0.2.jar:/Users/tokuhirom/.gradle/caches/modules-2/files-2.1/org.apache.commons/commons-math3/3.2/ec2544ab27e110d2d431bdad7d538ed509b21e62/commons-math3-3.2.jar:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar" com.intellij.rt.execution.application.AppMain com.example.ListBenchmark
# JMH 1.14.1 (released 8 days ago)
# VM version: JDK 1.8.0_77, VM 25.77-b03
# VM invoker: /Library/Java/JavaVirtualMachines/jdk1.8.0_77.jdk/Contents/Home/jre/bin/java
# VM options: -Didea.launcher.port=7536 -Didea.launcher.bin.path=/Applications/IntelliJ IDEA.app/Contents/bin -Dfile.encoding=UTF-8
# Warmup: 5 iterations, 1 s each
# Measurement: 5 iterations, 1 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Throughput, ops/time
# Benchmark: com.example.ListBenchmark.forSum

# Run progress: 0.00% complete, ETA 00:00:20
# Fork: 1 of 1
# Warmup Iteration   1: 2540968.934 ops/s
# Warmup Iteration   2: 2577273.184 ops/s
# Warmup Iteration   3: 2547454.985 ops/s
# Warmup Iteration   4: 2651007.557 ops/s
# Warmup Iteration   5: 2545635.805 ops/s
Iteration   1: 2640561.772 ops/s
Iteration   2: 2458074.014 ops/s
Iteration   3: 2367925.531 ops/s
Iteration   4: 2497981.362 ops/s
Iteration   5: 2299906.873 ops/s


Result "forSum":
  2452889.910 ±(99.9%) 501706.533 ops/s [Average]
  (min, avg, max) = (2299906.873, 2452889.910, 2640561.772), stdev = 130291.593
  CI (99.9%): [1951183.378, 2954596.443] (assumes normal distribution)


# JMH 1.14.1 (released 8 days ago)
# VM version: JDK 1.8.0_77, VM 25.77-b03
# VM invoker: /Library/Java/JavaVirtualMachines/jdk1.8.0_77.jdk/Contents/Home/jre/bin/java
# VM options: -Didea.launcher.port=7536 -Didea.launcher.bin.path=/Applications/IntelliJ IDEA.app/Contents/bin -Dfile.encoding=UTF-8
# Warmup: 5 iterations, 1 s each
# Measurement: 5 iterations, 1 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Throughput, ops/time
# Benchmark: com.example.ListBenchmark.streamSum

# Run progress: 50.00% complete, ETA 00:00:10
# Fork: 1 of 1
# Warmup Iteration   1: 399340.103 ops/s
# Warmup Iteration   2: 444224.252 ops/s
# Warmup Iteration   3: 370769.038 ops/s
# Warmup Iteration   4: 369619.858 ops/s
# Warmup Iteration   5: 365175.203 ops/s
Iteration   1: 361513.205 ops/s
Iteration   2: 353190.357 ops/s
Iteration   3: 372077.392 ops/s
Iteration   4: 354354.544 ops/s
Iteration   5: 358669.026 ops/s


Result "streamSum":
  359960.905 ±(99.9%) 29081.888 ops/s [Average]
  (min, avg, max) = (353190.357, 359960.905, 372077.392), stdev = 7552.474
  CI (99.9%): [330879.017, 389042.793] (assumes normal distribution)


# Run complete. Total time: 00:00:20

Benchmark                 Mode  Cnt        Score        Error  Units
ListBenchmark.forSum     thrpt    5  2452889.910 ± 501706.533  ops/s
ListBenchmark.streamSum  thrpt    5   359960.905 ±  29081.888  ops/s

Process finished with exit code 0

この、最後のブロックが結果になります。

Benchmark                 Mode  Cnt        Score        Error  Units
ListBenchmark.forSum     thrpt    5  2452889.910 ± 501706.533  ops/s
ListBenchmark.streamSum  thrpt    5   359960.905 ±  29081.888  ops/s

Mode thrpt はスループットを表しています。

この例でいうと、forSum は 2452889.910 ops/sec で実行可能ということになります。 streamSum は 359960.905 ops/sec なので、forSum の方が圧倒的に高速だということがわかります。

BenchmarkMode の指定

Benchmark 結果で、スループット以外も出力したい場合は、以下の様に @BenchmarkMode を指定します。

java
    @Benchmark
    @BenchmarkMode(Mode.All)
    public int forSum() throws MalformedURLException {
        int sum = 0;
        for (int i = 0; i < 1000; i++) {
            sum += i;
        }
        return sum;
    }

結果が以下のように細かく出力されます。用途によって使い分けるといいでしょう。

Benchmark                                    Mode     Cnt        Score        Error  Units
ListBenchmark.forSum                        thrpt       5  2487852.267 ± 560316.401  ops/s
ListBenchmark.streamSum                     thrpt       5   336313.403 ±  75214.358  ops/s
ListBenchmark.forSum                         avgt       5       ≈ 10⁻⁶                s/op
ListBenchmark.streamSum                      avgt       5       ≈ 10⁻⁶                s/op
ListBenchmark.forSum                       sample  160898       ≈ 10⁻⁶                s/op
ListBenchmark.forSum:forSum·p0.00          sample               ≈ 10⁻⁶                s/op
ListBenchmark.forSum:forSum·p0.50          sample               ≈ 10⁻⁶                s/op
ListBenchmark.forSum:forSum·p0.90          sample               ≈ 10⁻⁶                s/op
ListBenchmark.forSum:forSum·p0.95          sample               ≈ 10⁻⁶                s/op
ListBenchmark.forSum:forSum·p0.99          sample               ≈ 10⁻⁶                s/op
ListBenchmark.forSum:forSum·p0.999         sample               ≈ 10⁻⁵                s/op
ListBenchmark.forSum:forSum·p0.9999        sample               ≈ 10⁻⁴                s/op
ListBenchmark.forSum:forSum·p1.00          sample                0.006                s/op
ListBenchmark.streamSum                    sample  114387       ≈ 10⁻⁵                s/op
ListBenchmark.streamSum:streamSum·p0.00    sample               ≈ 10⁻⁶                s/op
ListBenchmark.streamSum:streamSum·p0.50    sample               ≈ 10⁻⁶                s/op
ListBenchmark.streamSum:streamSum·p0.90    sample               ≈ 10⁻⁵                s/op
ListBenchmark.streamSum:streamSum·p0.95    sample               ≈ 10⁻⁵                s/op
ListBenchmark.streamSum:streamSum·p0.99    sample               ≈ 10⁻⁵                s/op
ListBenchmark.streamSum:streamSum·p0.999   sample               ≈ 10⁻⁴                s/op
ListBenchmark.streamSum:streamSum·p0.9999  sample               ≈ 10⁻³                s/op
ListBenchmark.streamSum:streamSum·p1.00    sample                0.005                s/op
ListBenchmark.forSum                           ss       5       ≈ 10⁻⁵                s/op
ListBenchmark.streamSum                        ss       5       ≈ 10⁻⁴                s/op