FLINTERS Engineer's Blog

FLINTERSのエンジニアによる技術ブログ

マイクロベンチマークツールのsbt-jmhを使ってみた

越です。

最近すごく暑さが気になってきたので、夏休みを利用して、地元の長野で避暑地でも行ってゆっくりしようかと考えています。

scalaベンチマーク

scalaの勉強のためにscalaベンチマークを測る方法を調べていて、 scala.testing.BenchmarkがScala 2.11で廃止されていたのでsbt-jmhを試しに使ってみました。 Deprecate unmaintained/old classes for removal in 2.11

sbt-jmhを試すために作成したディレクトリ構成

.
├── build.sbt
├── project
│    └── plugins.sbt 
└── src
     └─ main
           └─ scala
                 └─ practiceJmh
                     └─ Practice.scala

sbt 定義ファイルを設定

githubのREADMEを参考にsbt 定義ファイルを設定

build.sbt

enablePlugins(JmhPlugin)

project/plugins.sbt

addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.2.1")

使い方

src/main/scala 配下の任意の .scala ファイルにクラスを作って、@Benchmarkをつけるだけ。

src/main/scala/practiceJmh/Practice.scala

package practiceJmh
import org.openjdk.jmh.annotations.Benchmark
import scala.collection._

class Practice {

  @Benchmark
  def useMapJmh(): Seq[String] = {
    useMap(10000)
  }

  @Benchmark
  def useBreakOutJmh(): Seq[String] = {
    useBreakOut(10000)
  }

  @Benchmark
  def useWhileJmh(): Seq[String] = {
    useWhile(10000)
  }

  def useMap(x: Int): Seq[String] = {
    1.to(x).map(x => s"Hello World! $x")
  }

  def useBreakOut(x: Int): Seq[String] = {
    1.to(x).map(x => s"Hello World! $x")(breakOut): Seq[String]
  }

  def useWhile(x: Int): Seq[String] = {
    var result: List[String] = List.empty
    val i = 1.to(x).iterator
    while(i.hasNext){
      result = s"Hello World! ${i.next}" :: result
    }
    result.reverse.toSeq
  }
}

ベンチマークの実行

プロジェクトのベースディレクトリで、sbt をコマンドライン引数なしで実行し、インタラクティブモードで起動する。

sbt

コンパイルする。

jmh:compile

実行する。

jmh:run -i 3 -wi 3 -f1 -t 1
オプション 意味
i 計測回数
wi ウォーミングアップ回数
f 全体(計測回数+ウォーミングアップ回数)の試行回数
t スレッド数

実行結果

[info] # Run complete. Total time: 00:00:19
[info] 
[info] Benchmark                             Mode  Cnt    Score     Error  Units
[info] practiceJmh.Practice.useBreakOutJmh  thrpt    3  549.210 ± 474.347  ops/s
[info] practiceJmh.Practice.useMapJmh       thrpt    3  588.116 ± 260.384  ops/s
[info] practiceJmh.Practice.useWhileJmh        thrpt    3  523.784 ± 359.510  ops/s

デフォルトではthrp(スループット)なので、Scoreは単位時間あたりの実行回数が表示されている(数字が大きいほうが良い)。 ただ、Error(誤差の範囲)が大きいので、計測回数とウォーミングアップ回数を増やして、再実行してみます。

jmh:run -i 30 -wi 10 -f1 -t 1
[info] # Run complete. Total time: 00:02:02
[info] 
[info] Benchmark                             Mode  Cnt    Score    Error  Units
[info] practiceJmh.Practice.useBreakOutJmh  thrpt   30  588.744 ± 34.702  ops/s
[info] practiceJmh.Practice.useMapJmh       thrpt   30  628.154 ± 13.763  ops/s
[info] practiceJmh.Practice.useWhile        thrpt   30  618.311 ± 15.134  ops/s

Error(誤差の範囲)が小さくなりました。

参考サイト

Micro Benchmark in Scala - Using sbt-jmh
ScalaでtoSetやtoListとかを使うよりはbreakOutした方が少し速い