10.05.2014

Micro Benchmark in Scala - Using sbt-jmh

Scala でマイクロベンチマーク - sbt-jmh を使ってみる

 

sbt-jmh は、sbt コンソールの中で、Java のマイクロベンチマークツールである jmh を扱えるようにする
sbt プラグインである。

Scala のマイクロベンチマークにデファクトスタンダードが存在するかどうかは分からないが、

といった経緯もあり、今回触れてみることにした。

 

環境
  • 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 バージョンのほうが良いスループットが出ることがわかった。

より高度な使い方は公式ドキュメントや以下を参照。

 

 

References

0 件のコメント:

コメントを投稿