覚えたら書く

IT関係のデベロッパとして日々覚えたことを書き残したいです。twitter: @yyoshikaw

JMH - Javaでのベンチマークを試してみる

OpenJDK のサイトで公開されているマイクロベンチマーク用のライブラリ。Java Microbenchmark Harness の略。


JMHの存在理由

Javaでパフォーマンス測定(ベンチマーク取得)するのは以下のような理由により簡単ではありません

  • 計測用のコードを書くのが実は難しい

    • 計測用のコードの書き方がまずいと、コンパイラにより最適化で処理部分を削ってしまう場合がある
  • ウォームアップが必要

    • JITコンパイラによってコンパイルされているかどうかで測定結果が大きく異なる

こういう問題を解消して計測してくれるのがJMHです。(OpenJDKで公開されているので、Java公式ツールといってよいでしょう)


使い方(概要)

JMHを使った計測の流れは大まかに以下の通りです(Mavenを用いてIDE上で実行する例です)

  1. JMHでの計測用のMavenプロジェクトを作成する  
  2. 計測したい処理をソースコード内に記述する  
  3. Mavenでコンパイルする(ベンチマーク用の実際の実行用コードが出力される)  
  4. 計測実行用のmainを記述して実行する  
  5. コンソールに計測結果が出力される


計測の流れ(詳細)

以下IDE(Eclipse)で試しています

JMH実行用のMavenプロジェクトの作成

  • Mavenプロジェクトを作成

  • JMH用のアーキタイプを追加(※これは初めてやるときだけ実施する作業です)

    f:id:nini_y:20160906213351p:plain

    f:id:nini_y:20160906213217p:plain

    項目 備考
    groupId  org.openjdk.jmh
    artifactId jmh-java-benchmark-archetype
    version 1.13 その時の最新の値を指定する
  • JMH用のアーキタイプの選択(上記で追加したアーキタイプを選択します)

    f:id:nini_y:20160906213438p:plain

  • 自プロジェクトのgroupId, artifactIdを適当に指定

    f:id:nini_y:20160906213945p:plain

  • 計測実行用プロジェクトの雛形が作成される(初期状態で MyBenchmark.java が生成されます。ここに計測用のコード等を追記していきます)

    f:id:nini_y:20160906213956p:plain

計測実行用のコード作成

  • pom.xmlに計測対象のライブラリの依存を記述(今回の場合はcommons langへの依存関係を追加しています)
<dependencies>
 ・・・
    <dependency>
        <groupId>commons-lang</groupId>
        <artifactId>commons-lang</artifactId>
        <version>2.6</version>
    </dependency>
</dependencies>
  • プロジェクト生成時に作成されているMyBenchmark.javaに計測対象のコードを記述する(ファイル名は変更してかまいません)
    • 計測対象のコード
    • 計測を実行するためのmain

計測対象のメソッドに@Benchmarkのアノテーションを付与してください。

  • Mavenでビルドする
    • mvn clean
    • mvn compile

Mavenビルドを行った際に、targetディレクトリ以下に測定用の実際のコードが出力されています

f:id:nini_y:20160906214535p:plain

計測実行

  • mainを実行します

  • 計測結果出力(コンソールに以下のような結果が出力されます

# Run complete. Total time: 00:01:13

Benchmark                          Mode  Cnt       Score       Error  Units
MyBenchmark.execFastDateFormat    thrpt   20  939757.324 ± 10030.632  ops/s
MyBenchmark.execSimpleDateFormat  thrpt   20  344164.449 ±  2542.729  ops/s

各メソッド単位に結果が出力されます。
 「Score」: 1秒間に平均何回実行できたの値(値が大きければ大きいほど良いということになる)
 「Error」: Score±Errorの値が99.9%信頼区間での値範囲となります


計測例

以下の日付のフォーマッティング用APIの中でどっちが早いのかを検証する

API 概要
java.text.SimpleDateFormat#format Java標準の日付フォーマットAPI(SimpleDateFormatはスレッドセーフではないことで有名)
org.apache.commons.lang.time.DateFormatUtils#format Apache Commons から提供された日付フォーマットAPI

■計測用のコード

package yy.sample;


import org.apache.commons.lang.time.DateFormatUtils;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.Mode;
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.text.SimpleDateFormat;
import java.util.Date;

public class MyBenchmark {

    static final Date d = new Date();

    @Benchmark
    public void execSimpleDateFormat() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
        String str = sdf.format(d);
    }

    @Benchmark
    public void execFastDateFormat() {
        String str = DateFormatUtils.format(d, "yyyy/MM/dd");
    }

    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
                // 実行対象のベンチマークが定義された"クラス名.メソッド名"を正規表現で指定
                .include(MyBenchmark.class.getSimpleName())
                .warmupIterations(15)
                .forks(1)
                .mode(Mode.Throughput)
                .build();
        new Runner(opt).run();
    }
}

■計測結果(1秒間で何回オペレーションが実行できているか)

# Run complete. Total time: 00:01:13

Benchmark                          Mode  Cnt       Score       Error  Units
MyBenchmark.execFastDateFormat    thrpt   20  939757.324 ± 10030.632  ops/s
MyBenchmark.execSimpleDateFormat  thrpt   20  344164.449 ±  2542.729  ops/s

SimpleDateFormatよりApache CommonsのDateFormatUtilsの方が早い



関連エントリ



関連リンク