OpenJDK のサイトで公開されているマイクロベンチマーク用のライブラリ。Java Microbenchmark Harness の略。
JMHの存在理由
Javaでパフォーマンス測定(ベンチマーク取得)するのは以下のような理由により簡単ではありません
計測用のコードを書くのが実は難しい
- 計測用のコードの書き方がまずいと、コンパイラにより最適化で処理部分を削ってしまう場合がある
ウォームアップが必要
- JITコンパイラによってコンパイルされているかどうかで測定結果が大きく異なる
こういう問題を解消して計測してくれるのがJMHです。(OpenJDKで公開されているので、Java公式ツールといってよいでしょう)
使い方(概要)
JMHを使った計測の流れは大まかに以下の通りです(Mavenを用いてIDE上で実行する例です)
- JMHでの計測用のMavenプロジェクトを作成する
↓ - 計測したい処理をソースコード内に記述する
↓ - Mavenでコンパイルする(ベンチマーク用の実際の実行用コードが出力される)
↓ - 計測実行用のmainを記述して実行する
↓ - コンソールに計測結果が出力される
計測の流れ(詳細)
以下IDE(Eclipse)で試しています
JMH実行用のMavenプロジェクトの作成
Mavenプロジェクトを作成
JMH用のアーキタイプを追加(※これは初めてやるときだけ実施する作業です)
項目 値 備考 groupId org.openjdk.jmh artifactId jmh-java-benchmark-archetype version 1.13 その時の最新の値を指定する JMH用のアーキタイプの選択(上記で追加したアーキタイプを選択します)
自プロジェクトのgroupId, artifactIdを適当に指定
計測実行用プロジェクトの雛形が作成される(初期状態で MyBenchmark.java が生成されます。ここに計測用のコード等を追記していきます)
計測実行用のコード作成
- 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ディレクトリ以下に測定用の実際のコードが出力されています
計測実行
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
の方が早い