Scala でマイクロベンチマーク - sbt-jmh を使ってみる
sbt-jmh は、sbt コンソールの中で、Java のマイクロベンチマークツールである jmh を扱えるようにする
sbt プラグインである。
Scala のマイクロベンチマークにデファクトスタンダードが存在するかどうかは分からないが、
- scala.testing.Benchmark は Scala 2.11 で廃止
- Google Caliper は最近メンテナンスがされていない?
といった経緯もあり、今回触れてみることにした。
環境
- OS: Mac OS X 10.9.4
- Scala: 2.11.2
- sbt: 0.13.5
- sbt-jmh: 0.1.6
sbt 定義ファイルの設定
まずは sbt の各種設定ファイルにプラグインの追加設定を行う。
- plugins.sbt
addSbtPlugin(
"pl.project13.scala"
%
"sbt-jmh"
%
"0.1.6"
)
- build.sbt (利用時のみ)
jmhSettings
- Build.scala (利用時のみ/一例)
import
pl.project
13
.scala.sbt.SbtJmh.
_
sbt.Project(...).settings(jmhSettings
:
_
*)
ベンチマーク対象コードの記述
src/main/scala 配下の任意の .scala ファイルに適当なクラスを作って(objectではダメ)、
測定したいメソッドにアノテーション @org.openjdk.jmh.annotations.Benchmark を付けるだけでよい。
- 例: 数値の Set/List を作成し、contains メソッドを実行するまでの所要時間を計測
123456789import
org.openjdk.jmh.annotations.Benchmark
class
ContainsBench {
@
Benchmark
def
setContains()
:
Unit
=
(
1
to
100000
).toSet.contains(
100001
)
@
Benchmark
def
listContains()
:
Unit
=
(
1
to
100000
).toList.contains(
100001
)
}
- クラス内で状態を保持したり、パラメータ付きのメソッドを定義したい場合は
@State など他のアノテーションの利用が必要
12345678910111213import
org.openjdk.jmh.annotations.{Benchmark, Scope, State}
@
State(Scope.Thread)
class
ContainsBench {
val
xs
=
(
1
to
100000
).toSet
val
ys
=
(
1
to
100000
).toList
@
Benchmark
def
setContains()
:
Unit
=
xs.contains(
100001
)
@
Benchmark
def
listContains()
:
Unit
=
ys.contains(
100001
)
}
ベンチマークの実行
コードの準備が終わったら、sbt を起動。(マルチプロジェクトの場合は対象プロジェクトへ移動)
run -l でベンチマーク一覧、run -h でヘルプが表示される (オプションは非常に豊富)。
- 全てのベンチマークの実行
run -i 3 -wi 3 -f1 -t1
-i でイテレーション回数、
-wi でウォームアップイテレーション(測定前に実行される繰り返し)回数を指定。
正確な測定を行うためには、それぞれ最低でも10〜20回を指定すべきとのこと。
-f はフォークする数の指定。この回数だけウォームアップ+実測が繰り返される。
-t はスレッド数。とりあえずは 1 を指定すれば良さそうだ。
また、いくつかの測定モードが用意されているが、デフォルトではスループット計測モードとなる。 - 特定のベンチマークの実行
公式ドキュメントに載っていた例。ワイルドカードを使えるようだ。run -i 3 -wi 3 -f1 -t1 .*FalseSharing.*
- 実行結果の例
contains メソッド自体は Set のほうが圧倒的に速いものの、toSet のコストが大きいため List バージョンのほうが良いスループットが出ることがわかった。[info] Running org.openjdk.jmh.Main -i 3 -wi 3 -f1 -t1
[info] # VM invoker: /Library/Java/JavaVirtualMachines/1.7.0.jdk/Contents/Home/jre/bin/java
[info] # VM options: <none>
[info] # Warmup: 3 iterations, 1 s each
[info] # Measurement: 3 iterations, 1 s each
[info] # Timeout: 10 min per iteration
[info] # Threads: 1 thread, will synchronize iterations
[info] # Benchmark mode: Throughput, ops/time
[info] # Benchmark: com.github.mogproject.util.ContainsBench.listContains
[info]
[info] # Run progress: 0.00% complete, ETA 00:00:12
[info] # Fork: 1 of 1
[info] # Warmup Iteration 1: 35.322 ops/s
[info] # Warmup Iteration 2: 40.904 ops/s
[info] # Warmup Iteration 3: 46.665 ops/s
[info] Iteration 1: 39.450 ops/s
[info] Iteration 2: 42.116 ops/s
[info] Iteration 3: 41.535 ops/s
[info]
[info]
[info] Result: 41.033 ±(99.9%) 25.573 ops/s [Average]
[info] Statistics: (min, avg, max) = (39.450, 41.033, 42.116), stdev = 1.402
[info] Confidence interval (99.9%): [15.461, 66.606]
[info]
[info]
[info] # VM invoker: /Library/Java/JavaVirtualMachines/1.7.0.jdk/Contents/Home/jre/bin/java
[info] # VM options: <none>
[info] # Warmup: 3 iterations, 1 s each
[info] # Measurement: 3 iterations, 1 s each
[info] # Timeout: 10 min per iteration
[info] # Threads: 1 thread, will synchronize iterations
[info] # Benchmark mode: Throughput, ops/time
[info] # Benchmark: com.github.mogproject.util.ContainsBench.setContains
[info]
[info] # Run progress: 50.00% complete, ETA 00:00:07
[info] # Fork: 1 of 1
[info] # Warmup Iteration 1: 4.550 ops/s
[info] # Warmup Iteration 2: 6.826 ops/s
[info] # Warmup Iteration 3: 6.980 ops/s
[info] Iteration 1: 6.730 ops/s
[info] Iteration 2: 6.901 ops/s
[info] Iteration 3: 6.798 ops/s
[info]
[info]
[info] Result: 6.810 ±(99.9%) 1.569 ops/s [Average]
[info] Statistics: (min, avg, max) = (6.730, 6.810, 6.901), stdev = 0.086
[info] Confidence interval (99.9%): [5.241, 8.380]
[info]
[info]
[info] # Run complete. Total time: 00:00:15
[info]
[info] Benchmark Mode Samples Score Score error Units
[info] c.g.m.u.ContainsBench.listContains thrpt 3 41.033 25.573 ops/s
[info] c.g.m.u.ContainsBench.setContains thrpt 3 6.810 1.569 ops/s
より高度な使い方は公式ドキュメントや以下を参照。
0 件のコメント:
コメントを投稿