2014/7/6追記)
sbt-jmhというsbt用のJMHプラグインができたそうなので、そちら向けのエントリを書きました。
sbt-jmhを使って、ScalaでマイクロベンチマークComments
http://d.hatena.ne.jp/Kazuhira/20140706/1404641244
先ほど、こんなエントリを投稿しました。
マイクロベンチマークツール、JMHを試す
http://d.hatena.ne.jp/Kazuhira/20140102/1388662362
この時、
$ mvn package
でJARファイルを作って実行するんだよ、みたいな内容を書きましたが、このJARの中身をよくよく見ると
$ jar -tvf target/microbenchmarks.jar 167 Thu Jan 02 20:21:18 JST 2014 META-INF/MANIFEST.MF 0 Thu Jan 02 20:21:18 JST 2014 META-INF/ 585 Thu Jan 02 20:21:18 JST 2014 META-INF/MicroBenchmarks 177 Thu Jan 02 20:21:18 JST 2014 META-INF/CompilerHints 0 Thu Jan 02 20:21:18 JST 2014 org/ 0 Thu Jan 02 20:21:18 JST 2014 org/littlewings/ 0 Thu Jan 02 20:21:18 JST 2014 org/littlewings/jmh/ 0 Thu Jan 02 20:21:18 JST 2014 org/littlewings/jmh/example/ 1354 Thu Jan 02 20:21:18 JST 2014 org/littlewings/jmh/example/TestState.class 0 Thu Jan 02 20:21:18 JST 2014 org/littlewings/jmh/example/generated/ 531 Thu Jan 02 20:21:18 JST 2014 org/littlewings/jmh/example/generated/TestState$TestState_jmh.class 7040 Thu Jan 02 20:21:18 JST 2014 org/littlewings/jmh/example/generated/HelloJmh$BlackHole_jmh_B1.class 19406 Thu Jan 02 20:21:18 JST 2014 org/littlewings/jmh/example/generated/TestState.class 7010 Thu Jan 02 20:21:18 JST 2014 org/littlewings/jmh/example/generated/Loop$Loop_jmh_B1.class 1403 Thu Jan 02 20:21:18 JST 2014 org/littlewings/jmh/example/generated/Loop$Loop_jmh_B2.class 7104 Thu Jan 02 20:21:18 JST 2014 org/littlewings/jmh/example/generated/TestState$BlackHole_jmh_B3.class 7042 Thu Jan 02 20:21:18 JST 2014 org/littlewings/jmh/example/generated/HelloJmh$HelloJmh_jmh_B1.class 521 Thu Jan 02 20:21:18 JST 2014 org/littlewings/jmh/example/generated/HelloJmh$HelloJmh_jmh.class 1448 Thu Jan 02 20:21:18 JST 2014 org/littlewings/jmh/example/generated/HelloJmh$BlackHole_jmh_B2.class 7099 Thu Jan 02 20:21:18 JST 2014 org/littlewings/jmh/example/generated/HelloJmh$BlackHole_jmh_B3.class 481 Thu Jan 02 20:21:18 JST 2014 org/littlewings/jmh/example/generated/Loop$Loop_jmh.class 1453 Thu Jan 02 20:21:18 JST 2014 org/littlewings/jmh/example/generated/TestState$TestState_jmh_B2.class 7054 Thu Jan 02 20:21:18 JST 2014 org/littlewings/jmh/example/generated/Loop$Loop_jmh_B3.class 506 Thu Jan 02 20:21:18 JST 2014 org/littlewings/jmh/example/generated/Loop$BlackHole_jmh.class 18988 Thu Jan 02 20:21:18 JST 2014 org/littlewings/jmh/example/generated/Loop.class 7104 Thu Jan 02 20:21:18 JST 2014 org/littlewings/jmh/example/generated/TestState$TestState_jmh_B3.class 7079 Thu Jan 02 20:21:18 JST 2014 org/littlewings/jmh/example/generated/Loop$BlackHole_jmh_B3.class 531 Thu Jan 02 20:21:18 JST 2014 org/littlewings/jmh/example/generated/TestState$BlackHole_jmh.class 7094 Thu Jan 02 20:21:18 JST 2014 org/littlewings/jmh/example/generated/HelloJmh$HelloJmh_jmh_B3.class 526 Thu Jan 02 20:21:18 JST 2014 org/littlewings/jmh/example/generated/HelloJmh$BlackHole_jmh.class 7044 Thu Jan 02 20:21:18 JST 2014 org/littlewings/jmh/example/generated/TestState$BlackHole_jmh_B1.class 15848 Thu Jan 02 20:21:18 JST 2014 org/littlewings/jmh/example/generated/HelloJmh.class 7024 Thu Jan 02 20:21:18 JST 2014 org/littlewings/jmh/example/generated/Loop$BlackHole_jmh_B1.class 7050 Thu Jan 02 20:21:18 JST 2014 org/littlewings/jmh/example/generated/TestState$TestState_jmh_B1.class 1453 Thu Jan 02 20:21:18 JST 2014 org/littlewings/jmh/example/generated/TestState$BlackHole_jmh_B2.class 1428 Thu Jan 02 20:21:18 JST 2014 org/littlewings/jmh/example/generated/Loop$BlackHole_jmh_B2.class 1443 Thu Jan 02 20:21:18 JST 2014 org/littlewings/jmh/example/generated/HelloJmh$HelloJmh_jmh_B2.class 839 Thu Jan 02 20:21:18 JST 2014 org/littlewings/jmh/example/Loop.class 576 Thu Jan 02 20:21:18 JST 2014 org/littlewings/jmh/example/HelloJmh.class 〜以降JMHやargs4jのクラスが続く〜
となっていて、作成したJavaのクラスは3つだったのに、大量のクラスが作成されています。
つまり、Pluggable Annotation Processing APIが使われてコンパイル時にクラスが生成されているわけですね…。
ここでふと思う。Scalaで使えない?
たぶんダメだろうと思いつつ、こんなsbtの定義と
build.sbt
import AssemblyKeys._ name := "jmh-example" version := "0.0.1-SNAPSHOT" scalaVersion := "2.10.3" organization := "org.littlewings" scalacOptions ++= Seq("-Xlint", "-deprecation") libraryDependencies += "org.openjdk.jmh" % "jmh-core" % "0.2" assemblySettings jarName in assembly := "microbenchmarks.jar" test in assembly := {} mainClass in assembly := Some("org.openjdk.jmh.Main")
sbt-assemblyプラグインの設定、
project/assembly.sbt
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.10.1")
そして、こんなScalaコードを用意します。
src/main/scala/org/littlewings/jmh/example/HelloJmhScala.scala
package org.littlewings.jmh.example import java.util.concurrent.TimeUnit import org.openjdk.jmh.annotations.GenerateMicroBenchmark class HelloJmhScala { @GenerateMicroBenchmark def simpleBench(): Unit = TimeUnit.MILLISECONDS.sleep(500L) }
で、JARファイルを作って…
> assembly [info] Updating {file:/xxxxx/}jmh-example... [info] Resolving org.fusesource.jansi#jansi;1.4 ... [info] Done updating. [info] Compiling 1 Scala source to /xxxxx/target/scala-2.10/classes... [info] Including: jmh-core-0.2.jar [info] Including: args4j-2.0.16.jar [info] Including: scala-library.jar [info] Checking every *.class/*.jar file's SHA-1. [info] Merging files... [warn] Merging 'LICENSE' with strategy 'rename' [warn] Merging 'META-INF/MANIFEST.MF' with strategy 'discard' [warn] Strategy 'discard' was applied to a file [warn] Strategy 'rename' was applied to a file [info] SHA-1: a26154cdfcae13645ae2bac5f365a1d85df412cf [info] Packaging /xxxxx/target/scala-2.10/microbenchmarks.jar ... [info] Done packaging. [success] Total time: 13 s, completed 2014/01/02 21:56:57
sbtを立ち上げているターミナルとは、別のターミナルから実行。
$ java -jar target/scala-2.10/microbenchmarks.jar No matching benchmarks. Miss-spelled regexp? Use -v for verbose output.
案の定、ベンチマークがないんだけど?と言われます。
本来は「-l」オプションでベンチマークの一覧が見れるのですが、何も出てきません。
$ java -jar target/scala-2.10/microbenchmarks.jar -l Benchmarks:
まあ、仕方がないですね。
ちなみに、sbt-assemblyプラグインは初めて使いました。
と、ここで諦めるのもなんなので、普通にJavaで被せました。以降、先のエントリの内容をJava+Scalaで淡々と移植していくので、JMH自体に興味のある方は前のエントリを参照してください。
HelloWorld的なサンプルの移植
計測対象。
src/main/scala/org/littlewings/jmh/example/HelloJmhTarget.scala
package org.littlewings.jmh.example import java.util.concurrent.TimeUnit object HelloJmhTarget { def invoke(): Unit = TimeUnit.MILLISECONDS.sleep(500L) }
ベンチマーク側。
src/main/java/org/littlewings/jmh/example/HelloJmh.java
package org.littlewings.jmh.example; import org.openjdk.jmh.annotations.GenerateMicroBenchmark; public class HelloJmh { @GenerateMicroBenchmark public void simpleBench() throws InterruptedException { HelloJmhTarget.invoke(); } }
これで、普通に実行できるようになります。
まずはassembly。
> assembly [info] Updating {file:/xxxxx/}jmh-example... [info] Resolving org.fusesource.jansi#jansi;1.4 ... [info] Done updating. [info] Compiling 4 Scala sources and 3 Java sources to /xxxxx/target/scala-2.10/classes... [warn] 警告: 注釈プロセッサ'org.openjdk.jmh.processor.internal.GenerateMicroBenchmarkProcessor'から-source '1.7'より小さいソース・バージョン'RELEASE_6'がサポートされています [warn] 警告: 注釈プロセッサ'org.openjdk.jmh.processor.internal.HelperMethodValidationProcessor'から-source '1.7'より小さいソース・バージョン'RELEASE_6'がサポートされています [warn] 警告: 注釈プロセッサ'org.openjdk.jmh.processor.internal.GroupValidationProcessor'から-source '1.7'より小さいソース・バージョン'RELEASE_6'がサポートされています [warn] 警告: 注釈プロセッサ'org.openjdk.jmh.processor.internal.CompilerControlProcessor'から-source '1.7'より小さいソース・バージョン'RELEASE_6'がサポートされています [warn] 警告4個 [info] Including: jmh-core-0.2.jar [info] Including: args4j-2.0.16.jar [info] Including: scala-library.jar [info] Checking every *.class/*.jar file's SHA-1. [info] Merging files... [warn] Merging 'LICENSE' with strategy 'rename' [warn] Merging 'META-INF/MANIFEST.MF' with strategy 'discard' [warn] Strategy 'discard' was applied to a file [warn] Strategy 'rename' was applied to a file [info] SHA-1: 4f5bb4dfa6ff9d99dc38d1971cb2a4234d4eaa7e [info] Packaging /xxxxx/target/scala-2.10/microbenchmarks.jar ... [info] Done packaging. [success] Total time: 10 s, completed 2014/01/02 22:44:46
JMHがサポートしているAnnotation Processorが、Java 6なんだけどと言われていますが、とりあえず気にしない…。
生成されたJARファイルを実行。
$ java -jar target/scala-2.10/microbenchmarks.jar -wi 3 -i 3 -f 1 -bm thrpt -tu s
結果。
Benchmark Mode Thr Count Sec Mean Mean error Units o.l.j.e.HelloJmh.simpleBench thrpt 1 3 1 2.000 0.000 ops/s
まあ、当たり前と言えば当たり前ですが…。
繰り返しと状態保持のサンプル移植
あとは、もう適当に移植…。
繰り返しのScala側。
src/main/scala/org/littlewings/jmh/example/LoopTarget.scala
package org.littlewings.jmh.example import java.util.concurrent.TimeUnit object LoopTarget { def method(limit: Int): Unit = (0 until limit).foreach(i => TimeUnit.MILLISECONDS.sleep(10)) }
ベンチマークのJava側。
src/main/java/org/littlewings/jmh/example/Loop.java
package org.littlewings.jmh.example; import org.openjdk.jmh.annotations.GenerateMicroBenchmark; import org.openjdk.jmh.annotations.OperationsPerInvocation; public class Loop { @GenerateMicroBenchmark @OperationsPerInvocation(1) public void invoke1() throws InterruptedException { LoopTarget.method(1); } @GenerateMicroBenchmark @OperationsPerInvocation(100) public void invoke100() throws InterruptedException { LoopTarget.method(100); } }
状態保持のサンプルの移植。
Scala側。
src/main/scala/org/littlewings/jmh/example/TestStateTarget.scala
package org.littlewings.jmh.example import scala.beans.BeanProperty class TestStateTarget { @BeanProperty var counter: Long = _ def methodA(): Unit = counter += 1 def methodB(): Unit = counter += 1 }
ベンチマークのJava側。
src/main/java/org/littlewings/jmh/example/TestState.java
package org.littlewings.jmh.example; import org.openjdk.jmh.annotations.GenerateMicroBenchmark; import org.openjdk.jmh.annotations.Level; import org.openjdk.jmh.annotations.Setup; import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.annotations.TearDown; @State(Scope.Thread) public class TestState { private TestStateTarget target = new TestStateTarget(); @Setup public void setup() { System.out.println("========== setup =========="); target.setCounter(0); } @GenerateMicroBenchmark public void methodA() { target.methodA(); } @GenerateMicroBenchmark public void methodB() { target.methodB(); } @TearDown public void tearDown() { System.out.println("========== teardown =========="); System.out.println("counter = " + target.getCounter()); } }
実行!
$ java -jar target/scala-2.10/microbenchmarks.jar -wi 3 -i 3 -f 1 -bm thrpt -tu s
結果。
Benchmark Mode Thr Count Sec Mean Mean error Units o.l.j.e.HelloJmh.simpleBench thrpt 1 3 1 2.000 0.000 ops/s o.l.j.e.Loop.invoke1 thrpt 1 3 1 89.689 7.197 ops/s o.l.j.e.Loop.invoke100 thrpt 1 3 1 91.989 10.924 ops/s o.l.j.e.TestState.methodA thrpt 1 3 1 401082658.122 83017505.408 ops/s o.l.j.e.TestState.methodB thrpt 1 3 1 354834340.622 199448544.886 ops/s
そんなにJavaと変わらなそうだから、大丈夫かな…。
とりあえず、sbt-assemblyプラグインと最低限のJavaのブリッジコードを書けば、Scala+sbtでもJMHは使えそうだということで。