CLOVER🍀

That was when it all began.

マむクロベンチマヌクツヌル、JMHを詊す

去幎、こんな蚘事が出おいお、個人的にはけっこう興味を匕きたした。

Javaのマむクロベンチマヌクツヌル「JMH」
http://acro-engineer.hatenablog.com/entry/2013/11/07/120606

以前、Javaのキャッシュのベンチマヌクを取るのにGoogle Caliperの0.5を䜿っお遊んだこずがあるのですが、1.0になっおどうも倉な動きになっおいお諊めおいたずころに出おきた蚘事だったので、倧倉参考になりたした。

で、詊そう詊そうず思っおいるうちに、気付けば幎が倉わっおいたしたがヌ。

た、今からでも、やっおみたしょう。

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

準備

元蚘事にはJMH自䜓をビルドする方法から玹介されおいたすが、今はMaven Centralに0.2たでアップされおいるので、こちらを䜿甚するこずにしたす。

Maven䟝存関係は、以䞋のように定矩したす。

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

たた、ビルドしおJARを生成するのですが、この時JMH自䜓をJARに含める必芁があるため、buildplugins配䞋に以䞋の蚭定を加えたす。

      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-shade-plugin</artifactId>
        <version>2.2</version>
        <executions>
          <execution>
            <phase>package</phase>
            <goals>
              <goal>shade</goal>
            </goals>
            <configuration>
              <finalName>microbenchmarks</finalName>
              <transformers>
                <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                  <mainClass>org.openjdk.jmh.Main</mainClass>
                </transformer>
              </transformers>
            </configuration>
          </execution>
        </executions>
      </plugin>

あずは、マむクロベンチマヌクを行うためのコヌドを甚意したす。

この蚭定で、゜ヌスコヌドを曞いた埌に以䞋のコマンドを実行するず

$ mvn package

targetディレクトリ配䞋に「microbenchmarks.jar」が生成されるので、こちらを䜿甚したす。

実行コマンドは、こんな感じです。

$ java -jar target/microbenchmarks.jar [オプション]

では、いく぀かサンプルを玹介するず共に、実行䟋を茉せおいきたしょう。

なお、JMH自䜓のサンプルコヌドは、こちらで確認するこずができたす。

http://hg.openjdk.java.net/code-tools/jmh/file/tip/jmh-samples/src/main/java/org/openjdk/jmh/samples/

もしくは、リポゞトリ自䜓をMercurialでチェックアりトしたしょう。

$ hg clone http://hg.openjdk.java.net/code-tools/jmh/ jmh

HelloWorld的な

では、たずは単玔な凊理を曞いおみたす。
src/main/java/org/littlewings/jmh/example/HelloJmh.java

package org.littlewings.jmh.example;

import java.util.concurrent.TimeUnit;

import org.openjdk.jmh.annotations.GenerateMicroBenchmark;

public class HelloJmh {
    @GenerateMicroBenchmark
    public void simpleBench() throws InterruptedException {
        TimeUnit.MILLISECONDS.sleep(500);
    }
}

ベンチマヌク察象のメ゜ッドには、@GenerateMicroBenchmarkアノテヌションを付䞎したす。ひず぀のクラスに、耇数の@GenerateMicroBenchmarkアノテヌションを付䞎したメ゜ッドを蚭けお倧䞈倫です。

今回は、1回の呌び出しで500msecスリヌプする凊理を甚意したした。

それでは、ビルドしお実行しおみたす。

$ java -jar target/microbenchmarks.jar -wi 3 -i 3 -f 1 -r 1s -bm thrpt -tu s

実行途䞭にはこのような進捗状態が衚瀺され、

# Fork: 1 of 1
# Warmup: 3 iterations, 1 s each
# Measurement: 3 iterations, 1 s each
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Throughput, ops/time
# Running: org.littlewings.jmh.example.HelloJmh.simpleBench
# Warmup Iteration   1: 2.000 ops/s
# Warmup Iteration   2: 2.000 ops/s
# Warmup Iteration   3: 2.000 ops/s
Iteration   1: 2.000 ops/s
Iteration   2: 2.000 ops/s
Iteration   3: 2.000 ops/s

Result : 2.000 ±(99.9%) 0.000 ops/s
  Statistics: (min, avg, max) = (2.000, 2.000, 2.000), stdev = 0.000
  Confidence interval (99.9%): [2.000, 2.000]

最終的に、こんな結果が衚瀺されたす。

Benchmark                        Mode Thr     Count  Sec         Mean   Mean error    Units
o.l.j.e.HelloJmh.simpleBench    thrpt   1         3    1        2.000        0.000    ops/s

今回は衚瀺を秒単䜍にしおあるので、1秒で2回の呌び出し たあ、そうですね。

起動時にいろいろオプションを指定しおいたすが、実際には指定しなくおもデフォルトでいろいろ動いおくれたす。指定したオプションの意味を解説しおおくず

オプション 意味 指定可胜な倀
-wi りォヌムアップ時の繰り返し回数蚈枬には含たれない intで指定できる数字䟋3
-i 蚈枬を行う回数 intで指定できる数字䟋3
-f フォヌクの数。ここでは、ベンチマヌクのセットりォヌムアップから蚈枬たでの繰り返し回数ず捉えればOK intで指定できる数字䟋1
-r 蚈枬を行う際の、実行時間 100s、 200msなど
-bm ベンチマヌクのモヌド thrptスルヌプット、avgt平均、sampleTime distribution, percentile estimation、ssシングルショット、all巊に䞊げたモヌドを党郚実行する
-tu 結果衚瀺の際の、時間の単䜍 m、 s、 ms、 us、 ns

指定可胜なオプションの䞀芧は、以䞋のコマンドで芋るこずができたす。

$ java -jar target/microbenchmarks.jar -h

デフォルト倀に぀いおは、以䞋を芋るずいいでしょう。
暙準偏差
http://hg.openjdk.java.net/code-tools/jmh/file/tip/jmh-core/src/main/java/org/openjdk/jmh/runner/parameters/Defaults.java

繰り返し実行

ベンチマヌクでの枬定察象を耇数呌び出す際に、䜕床の呌び出しを1回の蚈枬ず捉えるかをJMHに指定するこずができたす。

以䞋、コヌド䟋。
src/main/java/org/littlewings/jmh/example/Loop.java

package org.littlewings.jmh.example;

import java.util.concurrent.TimeUnit;

import org.openjdk.jmh.annotations.GenerateMicroBenchmark;
import org.openjdk.jmh.annotations.OperationsPerInvocation;

public class Loop {
    public void method(int limit) throws InterruptedException {
        for (int i = 0; i < limit; i++) {
            TimeUnit.MILLISECONDS.sleep(10);
        }
    }

    @GenerateMicroBenchmark
    @OperationsPerInvocation(1)
    public void invoke1() throws InterruptedException {
        method(1);
    }

    @GenerateMicroBenchmark
    @OperationsPerInvocation(100)
    public void invoke100() throws InterruptedException {
        method(100);
    }
}

本圓に蚈枬したいのはmethodメ゜ッドだずした堎合、蚈枬甚のメ゜ッドずしおinvoke1ずinvoke100の2぀のメ゜ッドを甚意したす。invoke1は1回の呌び出しで本圓に蚈枬したいメ゜ッドをメ゜ッドを1回呌んだこずに、invoke100は1回の呌び出しで本圓に蚈枬したいメ゜ッドを100回呌んでいるこずを、JMHに教えおいるこずになっおいたす。

method内では、指定のルヌプごずに10msecスリヌプしたす。なので、invoke100メ゜ッドを呌び出すず1秒少々スリヌプするこずになりたすね。

これは、ベンチマヌクモヌドをallにしお結果を芋おみたしょう。

$ java -jar target/microbenchmarks.jar -wi 3 -i 3 -f 1 -r 1s -bm all -tu s

こんな感じになりたす。

Benchmark                        Mode Thr     Count  Sec         Mean   Mean error    Units
o.l.j.e.Loop.invoke1            thrpt   1         3    1       87.800       40.489    ops/s
o.l.j.e.Loop.invoke100          thrpt   1         3    1       90.289       10.820    ops/s
o.l.j.e.Loop.invoke1             avgt   1         3    1        0.011        0.001     s/op
o.l.j.e.Loop.invoke100           avgt   1         3    1        0.011        0.000     s/op
o.l.j.e.Loop.invoke1           sample   1       258    1        0.012        0.000     s/op
o.l.j.e.Loop.invoke100         sample   1         3    1        1.191        0.054     s/op
o.l.j.e.Loop.invoke1               ss   1         3    0        0.014        0.041        s
o.l.j.e.Loop.invoke100             ss   1         3    0        1.107        0.130        s

スルヌプットは、100回呌び出しの方が、若干遅いくらい。平均は同じですね。なので、JMH偎で@GenerateMicroBenchmarkを付䞎したメ゜ッド自身が、蚈枬察象を100回呌んだこずを認識しおいたす。代わりに、シングルショットssは、スリヌプの結果がダむレクトに珟れおいるのがわかりやすいず思いたす。

ベンチマヌクに状態を持たせる

ベンチマヌクのクラスにフィヌルドなどの状態を持たせお、その初期化終了などをコントロヌルできたす。

では、サンプルを。
src/main/java/org/littlewings/jmh/example/TestState.java

package org.littlewings.jmh.example;

import java.util.concurrent.TimeUnit;

import org.openjdk.jmh.annotations.GenerateMicroBenchmark;
import org.openjdk.jmh.annotations.Level;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.TearDown;

@State(Scope.Thread)
public class TestState {
    private long counter = 0;

    @Setup
    //@Setup(Level.Iteration)
    public void setup() {
        System.out.println("========== setup ==========");
        counter = 0;
    }

    @GenerateMicroBenchmark
    public void methodA() {
        counter++;
    }

    @GenerateMicroBenchmark
    public void methodB() {
        counter++;
    }

    @TearDown
    //@TearDown(Level.Iteration)
    public void tearDown() {
        System.out.println("========== teardown ==========");
        System.out.println("counter = " + counter);
    }
}

最初に、

@State(Scope.Thread)
public class TestState {

ず宣蚀しおいるずころがポむントです。これで、スレッド単䜍にベンチマヌクを持぀ようにしたす。

その他、必須ではありたせんが@Setupアノテヌションや@TearDownアノテヌションを付䞎したメ゜ッドを䜜成するこずで、初期化終了凊理を曞くこずができたす。

    @Setup
    //@Setup(Level.Iteration)
    public void setup() {
        System.out.println("========== setup ==========");
        counter = 0;
    }

    @TearDown
    //@TearDown(Level.Iteration)
    public void tearDown() {
        System.out.println("========== teardown ==========");
        System.out.println("counter = " + counter);
    }

JMHのサンプルでは、@TearDownアノテヌションを付䞎したメ゜ッドでアサヌションを行っおいたした。

@Setupアノテヌション、@TearDownアノテヌションでコメントアりトでLevelを曞いおいたすが、これには3぀のパタヌンを指定できたす。それぞれ、

が指定できたす。が、通垞、TrialかIterationを指定するんだず思いたす。Invocationは、Javadocでものすっごい譊告されおいたす 。

http://hg.openjdk.java.net/code-tools/jmh/file/tip/jmh-core/src/main/java/org/openjdk/jmh/annotations/Level.java

で、今回の䟋は@Setupず@TearDownでSystem.outしおいるだけなので、実行するず

$ java -jar target/microbenchmarks.jar -wi 3 -i 3 -f 1 -r 1s -bm thrpt -tu s

ちょっずレポヌトの結果に割り蟌たれおいたすが、こんな衚瀺になりたす。

# Fork: 1 of 1
========== setup ==========
# Warmup: 3 iterations, 1 s each
# Measurement: 3 iterations, 1 s each
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Throughput, ops/time
# Running: org.littlewings.jmh.example.TestState.methodB
# Warmup Iteration   1: 393825934.400 ops/s
# Warmup Iteration   2: 384291389.050 ops/s
# Warmup Iteration   3: 402733190.600 ops/s
Iteration   1: 393833628.933 ops/s
Iteration   2: 403786121.067 ops/s
Iteration   3: ========== teardown ==========
counter = 2387745388
404992957.983 ops/s


# Fork: 1 of 1
========== setup ==========
# Warmup: 3 iterations, 1 s each
# Measurement: 3 iterations, 1 s each
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Throughput, ops/time
# Running: org.littlewings.jmh.example.TestState.methodA
# Warmup Iteration   1: 384264742.033 ops/s
# Warmup Iteration   2: 380980549.233 ops/s
# Warmup Iteration   3: 354685731.983 ops/s
Iteration   1: 355758333.350 ops/s
Iteration   2: 383169984.083 ops/s
Iteration   3: ========== teardown ==========
counter = 2205967045
345683238.517 ops/s

Result : 361537185.317 ±(99.9%) 250075812.730 ops/s
  Statistics: (min, avg, max) = (345683238.517, 361537185.317, 383169984.083), stdev = 19400009.558
  Confidence interval (99.9%): [111461372.587, 611612998.047]

぀たり、デフォルトはTrialです。りォヌムアップの前に@Setupアノテヌションを付䞎したメ゜ッドが呌び出され、党郚が終わるず@TearDownを付䞎したメ゜ッドが呌び出されたす。

たた、同じクラスの@GenerateMicroBenchmarkアノテヌションを付䞎したメ゜ッド単䜍に、ベンチマヌクは実行されるのでこの単䜍で呌び出されたす。

では、ここでIterationに倉えお

    @Setup(Level.Iteration)

    @TearDown(Level.Iteration)

実行。

# Fork: 1 of 1
========== setup ==========# Warmup: 3 iterations, 1 s each
# Measurement: 3 iterations, 1 s each
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Throughput, ops/time
# Running: org.littlewings.jmh.example.TestState.methodA

# Warmup Iteration   1: ========== teardown ==========
counter = 393415410
========== setup ==========
393832584.250 ops/s
# Warmup Iteration   2: ========== teardown ==========
counter = 393429045
389109403.767 ops/s========== setup ==========

# Warmup Iteration   3: ========== teardown ==========
counter = 388305539
387911634.900 ops/s
Iteration   1: ========== setup ==========
========== teardown ==========
counter = 396708771
396541675.083 ops/s
Iteration   2: ========== setup ==========
========== teardown ==========
counter = 396693148
396416333.250 ops/s
========== setup ==========
Iteration   3: ========== teardown ==========
counter = 387101506
387070113.833 ops/s

Result : 393342707.389 ±(99.9%) 70028758.090 ops/s
  Statistics: (min, avg, max) = (387070113.833, 393342707.389, 396541675.083), stdev = 5432586.868
  Confidence interval (99.9%): [323313949.299, 463371465.479]


# Fork: 1 of 1
========== setup ==========
# Warmup: 3 iterations, 1 s each
# Measurement: 3 iterations, 1 s each
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Throughput, ops/time
# Running: org.littlewings.jmh.example.TestState.methodB
# Warmup Iteration   1: ========== teardown ==========
counter = 382855076
381309718.083 ops/s
# Warmup Iteration   2: ========== setup ==========
========== teardown ==========
counter = 405691075
407024043.733 ops/s
# Warmup Iteration   3: ========== setup ==========
========== teardown ==========
counter = 380215869
379928576.517 ops/s
Iteration   1: ========== setup ==========
========== teardown ==========
counter = 365053007
364777073.200 ops/s
Iteration   2: ========== setup ==========
========== teardown ==========
counter = 408294746
407903593.383 ops/s
========== setup ==========Iteration   3: 
========== teardown ==========
counter = 412189352
411940886.967 ops/s

Result : 394873851.183 ±(99.9%) 336991524.691 ops/s
  Statistics: (min, avg, max) = (364777073.200, 394873851.183, 411940886.967), stdev = 26142627.423
  Confidence interval (99.9%): [57882326.493, 731865375.874]

りォヌムアップず、各むテレヌション毎に@Setup@TearDownが呌び出されるようになりたす。

たあ、このくらいできればいいかな 。

なお、コマンドラむンのオプションで指定したパラメヌタなどは、アノテヌションでベンチマヌク察象のメ゜ッドやクラスに指定するこずもできたす。詳しくは、以䞋に定矩されおいるアノテヌションや列挙型

http://hg.openjdk.java.net/code-tools/jmh/file/tip/jmh-core/src/main/java/org/openjdk/jmh/annotations/

そしおサンプルコヌドで䜿い方を芋おみたしょう。

http://hg.openjdk.java.net/code-tools/jmh/file/tip/jmh-samples/src/main/java/org/openjdk/jmh/samples/

これで、JMHずGatlingが䜿えるようになった ずいいなぁ。