Javaによるマイクロベンチマーク
1前書き
このクイック記事はJMH(Java Microbenchmark Harness)に焦点を当てています。これは、今後のJava 9リリースでJVMの一部になる予定です。
簡単に言うと、JMHはJVMのウォームアップやコード最適化パスなどの処理を行い、ベンチマークをできる限り簡単にします。
2入門
はじめに、実際にJava 8を使い続け、依存関係を定義するだけです。
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>1.19</version>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>1.19</version>
</dependency>
JMH Core
およびhttps://search.maven.org/の最新バージョンclassic/#artifactdetails%7Corg.openjdk.jmh%7Cjmh-generator-annprocess%7C1.19%7Cjar[JMHアノテーションプロセッサ]はMaven Centralにあります。
次に、
@ Benchmark
アノテーションを使用して(任意のパブリッククラスで)簡単なベンチマークを作成します。
@Benchmark
public void init() {
//Do nothing
}
次に、ベンチマークプロセスを開始するメインクラスを追加します。
public class BenchmarkRunner {
public static void main(String[]args) throws Exception {
org.openjdk.jmh.Main.main(args);
}
}
BenchmarkRunner
を実行すると、おそらく無駄なベンチマークが実行されます。実行が完了すると、要約表が表示されます。
# Run complete. Total time: 00:06:45
Benchmark Mode Cnt Score Error Units
BenchMark.init thrpt 200 3099210741.962 ± 17510507.589 ops/s
3ベンチマークの種類
JMHはいくつかの可能なベンチマークをサポートします:
Throughput、
AverageTime、
SampleTime
、および
SingleShotTime
。これらは
@ BenchmarkMode
アノテーションで設定できます。
@Benchmark
@BenchmarkMode(Mode.AverageTime)
public void init() {
//Do nothing
}
結果の表には、(スループットの代わりに)平均時間メトリックが入ります。
# Run complete. Total time: 00:00:40
Benchmark Mode Cnt Score Error Units
BenchMark.init avgt 20 ≈ 10−9 s/op
4ウォームアップと実行の設定
@ Fork
アノテーションを使用して、ベンチマークの実行方法を設定できます。たとえば、
value
パラメータはベンチマークを実行する回数を制御し、
warmup
パラメータはベンチマークを結果を収集する前に何回実行するかを制御します。 :
@Benchmark
@Fork(value = 1, warmups = 2)
@BenchmarkMode(Mode.Throughput)
public void init() {
//Do nothing
}
これにより、JMHは2つのウォームアップフォークを実行し、結果を破棄してからリアルタイムベンチマークに進むように指示されます。
また、
@ Warmup
アノテーションを使用して、ウォームアップの反復回数を制御することもできます。たとえば、
@ Warmup(iterations = 5)
は、デフォルトの20回ではなく、5回のウォームアップ反復で十分であることをJMHに伝えます。
5状態
それでは、
State
を利用することで、ハッシュアルゴリズムのベンチマークを取るという、それほど簡単ではなく、より指示的なタスクを実行する方法を検討しましょう。
パスワードを数百回ハッシュすることで、パスワードデータベースに対する辞書攻撃からの保護を強化することにしたとします。
State
オブジェクトを使用してパフォーマンスへの影響を調べることができます。
@State(Scope.Benchmark)
public class ExecutionPlan {
@Param({ "100", "200", "300", "500", "1000" })
public int iterations;
public Hasher murmur3;
public String password = "4v3rys3kur3p455w0rd";
@Setup(Level.Invocation)
public void setUp() {
murmur3 = Hashing.murmur3__128().newHasher();
}
}
ベンチマークの方法は次のようになります。
@Fork(value = 1, warmups = 1)
@Benchmark
@BenchmarkMode(Mode.Throughput)
public void benchMurmur3__128(ExecutionPlan plan) {
for (int i = plan.iterations; i > 0; i--) {
plan.murmur3.putString(plan.password, Charset.defaultCharset());
}
plan.murmur3.hash();
}
ここでは、ベンチマークメソッドに渡されると、フィールド
iterations
にJMHによって
@ Param
アノテーションから適切な値が入力されます。
@ Setup
アノテーション付きメソッドはベンチマークの各呼び出しの前に呼び出され、分離を確実にする新しい
Hasher
を作成します。
実行が終了すると、以下のような結果が得られます。
# Run complete. Total time: 00:06:47
Benchmark (iterations) Mode Cnt Score Error Units
BenchMark.benchMurmur3__128 100 thrpt 20 92463.622 ± 1672.227 ops/s
BenchMark.benchMurmur3__128 200 thrpt 20 39737.532 ± 5294.200 ops/s
BenchMark.benchMurmur3__128 300 thrpt 20 30381.144 ± 614.500 ops/s
BenchMark.benchMurmur3__128 500 thrpt 20 18315.211 ± 222.534 ops/s
BenchMark.benchMurmur3__128 1000 thrpt 20 8960.008 ± 658.524 ops/s
5結論
このチュートリアルでは、Javaのマイクロベンチマークハーネスを中心に説明しました。
いつものように、コード例はhttps://github.com/eugenp/tutorials/tree/master/jmh[on GitHub]にあります。