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.project13.scala.sbt.SbtJmh._ sbt.Project(...).settings(jmhSettings: _*)
ベンチマーク対象コードの記述
src/main/scala 配下の任意の .scala ファイルに適当なクラスを作って(objectではダメ)、
測定したいメソッドにアノテーション @org.openjdk.jmh.annotations.Benchmark を付けるだけでよい。
- 例: 数値の Set/List を作成し、contains メソッドを実行するまでの所要時間を計測
import 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 など他のアノテーションの利用が必要
import 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.*
- 実行結果の例
[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
contains メソッド自体は Set のほうが圧倒的に速いものの、toSet のコストが大きいため List バージョンのほうが良いスループットが出ることがわかった。
より高度な使い方は公式ドキュメントや以下を参照。
0 件のコメント:
コメントを投稿