uehaj's blog

Grな日々 - GroovyとかGrailsとかElmとかRustとかHaskellとかReactとかFregeとかJavaとか -

Groovy 2.0でindyを使う

Java 7では、Java VM上での動的言語の実行を効率化することを目的とした一連の機能拡張(JSR 292: Supporting Dynamically Typed Languages on the Java Platform,日本語解説)がなされました。具体的にはたとえば、Java Bytecode命令の1つにinvokedynamicという命令が追加されました。このinvokedynamicの命令名からとって、これらの機能拡張のことを「indy」と略することが多いようなので、本記事でもそう呼びます。

indyがターゲットとするところの「Java VM上での動的言語」の1つであるGroovyでも、バージョン2(Groovy 2.0)からindy対応になっとったわけです。しかし、Groovy 2.0のindy対応は、デフォルトでは有効ではなく、有効にするにはちょっとした設定が必要です*1

本エントリではその方法について解説します。

準備

Groovyでindyを有効化するには以下が必要となります。

  • Groovy 2.0以降。当然必要です。ダウンロードしてきます。バイナリでもソース版リリースでもどちらでも大丈夫です。
    • ソース版リリースでは、gradlew installGroovyでindy版ライブラリをコンパイルすることができます。特にindyのためのオプションは必要ない様です。後述。
    • バイナリ版にもindy版ライブラリが含まれています。
  • Java SE 7。当然必要です。新しい方が良いようです。なお環境変数JAVA_HOMEに、Java SE7がインストールされたディレクトリが設定されている必要があります。

indyを有効にするための設定

やるべきことは2つあります。

indyのためにその1: --indyオプション

groovyコマンド、もしくはgroovycコマンドでgroovyプログラムを実行する場合、indyを有効化するためには、これらのコマンドの実行において「--indy」オプションを指定することが必要です。

なお--indyオプションを指定すると、内部的には、内部的に実行されるGroovyコンパイラ設定を表すorg.codehaus.groovy.control.CompilerConfigurationオブジェクトに対して

  .getOptimizationOptions().put("indy", true);

が設定されます。GroovyShellなどを使ってGroovyコードを実行する場合も、同様にCompilerConfigurationを設定すれば良いわけです。

indyのためにその2: indy対応のライブラリを使用する

さて、--indyオプションだけでは以下のようなエラーになります。

$ groovy -indy -e "println 'hello' "             
org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed:
General error during class generation: Cannot use invokedynamic, indy module was excluded from this build.

groovy.lang.GroovyRuntimeException: Cannot use invokedynamic, indy module was excluded from this build.

こうなる理由は、groovy-2.0.x.jarがindy対応のものではないからです。
対処としては、以下の2つの方法があります。

  • indy対応ライブラリを使用する方法1:groovy-all-2.0.x.jar, groov-2.0.x.jarを置き変える

この記事によれば、以下の上書きコピーを行います(元に戻せるようにコピー先はバックアップしといた方が良いでしょう)。

コピー元 コピー先
/indy/groovy-2.0.x-indy.jar /lib/groovy-2.0.x.jar

自分のアプリケーションなどにgroovy-all-x.x.x.jarを組み込んで使っていた場合などは、
/embeddable/groovy-all-2.0.x.jarの代りに
/embeddable/groovy-all-2.0.x-indy.jarを使用するようにします。

/indy配下には他にも、-indyを接尾辞とするjarファイル群(*-indy.jar)があるので、必要に応じてそれらもファイル名を変えてコピーする必要があると思われます。ちなみにこの*-indy.jarは何かというと、groovyコードを--indyオプションつきでgroovycコンパイルかけたものです。ランタイムのうち、groovyで書かれたものについて、indyを使用している版のclass file群ということです。「-indy」なしのただのjarは、今まで通りinvokedynamicを使用しないでコンパイルされたものなのですが、「-indyオプション」の配下でも別に実行できないわけではありません*2

寄り道して、これらの-indy.jarで、本当にinvokedynamic命令が使用されているかを確認してみます。

$ javap  -c -classpath /tool/groovy-2.0.1/target/install/indy/groovy-ant-2.0.1-indy.jar   groovy.util.FileNameFinder | grep dyn
      30: invokedynamic #56,  0             // InvokeDynamic #0:getFileNames:(Lgroovy/util/FileNameFinder;Ljava/util/Map;)Ljava/lang/Object;
      41: invokedynamic #56,  0             // InvokeDynamic #0:getFileNames:(Lgroovy/util/FileNameFinder;Ljava/util/Map;)Ljava/lang/Object;
      42: invokedynamic #99,  0             // InvokeDynamic #1:fileScanner:(Ljava/lang/Object;Lgroovy/lang/Closure;)Ljava/lang/Object;
      69: invokedynamic #106,  0            // InvokeDynamic #1:iterator:(Ljava/lang/Object;)Ljava/lang/Object;
     107: invokedynamic #119,  0            // InvokeDynamic #1:getAbsolutePath:(Ljava/lang/Object;)Ljava/lang/Object;
     112: invokedynamic #123,  0            // InvokeDynamic #1:leftShift:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;

おお。確かに。

  • indy対応ライブラリを使用する方法2: groovy-starter.confを書き変える

これは適当に自分で試して見付けた方法なので、保証なしです。でも、上のようにファイル名を変えて上書きコピーとかするよりもマシな気がしています。

/conf/groovy-starter.confを以下のように書き変えます。
書き変える内容は、「load !{groovy.home}/indy/*.jar」を追加するということです。

% diff -c target/install/conf/groovy-starter.conf~ target/install/conf/groovy-starter.conf 
*** target/install/conf/groovy-starter.conf.old	2012-08-13 16:50:29.000000000 +0900
--- target/install/conf/groovy-starter.conf	2012-08-13 16:56:29.000000000 +0900
***************
*** 15,20 ****
--- 15,21 ----
  
      # load required libraries
      load !{groovy.home}/lib/*.jar
+     load !{groovy.home}/indy/*.jar
  
      # load user specific libraries
      load !{user.home}/.groovy/lib/*.jar

この状態で

$ groovy -indy -e 'println "hoge" '                                                    
hoge

ぱちぱち。

効果のほどは?

今、ベンチマークを試してますが、jdk 7u5に関して、多少速くなる時もあり、むしろ遅くなるときもあり。CompileStaticほどの向上効果は得られていないですね。--indyをつけると既存コードが実行できなくなるバグらしきものも1個だけ発見しました。まとめて別途報告します。もちろんこれは今の最適化しきれてないとも言われていjvmの、かつ私が試した範囲のはなしなので一般化しないでくださいますよう。

番外編

Groovyをソースコードからコンパイルをする

groovy 2.0からは、build.xmlが除去されてます。なので、gradleでコンパイルする必要があります。といっても何も心配する必要はありません。みんな大好きgradlew(Gradle wrapper)が用意されているからです。

以前、ant時代にgroovyのコンパイルに使用していた「ant install」に相当する処理(/target/install配下にコンパイルする)は、gradleでは以下で実行できるようです。試した限りでは、indyに関する設定はしないでも良い(-indy.jarが生成される)ようです。

./gradlew installGroovy

ただのinstallタスクは、mavenのリポジトリへのインストールになるようです。

Gradleのビルドスクリプト中でgroovycに--indyオプションを与える

ちなみに、Gradleのビルドスクリプト中からgroovycを呼び出してコンパイルする際にgroovyに--indyオプションを与えるためには、

compileGroovy {
    groovyOptions.optimizationOptions.indy=true
}

こういう指定をしておけば良いです。(このオプションoptimizationOptions.indyはGradle 1.1から利用可能)

groovyのMavenモジュール(pom)を使う。

groovy自体をMavenのリポジトリからダウンロードして使用する場合、indy版のjarはclassifier(バリエーションのようなもの)として登録してあります。例えば、gradleであれば、

dependencies {
    groovy group: 'org.codehaus.groovy', name: 'groovy-all', version:'2.0.1', classifier:'indy'
}

のようにclassifier:'indy'を指定するとindy対応のgroovy-allが利用されるようになります。

*1:@nagai_masatoさんも[http://nagaimasato-ja.blogspot.jp/2012/05/invokedynamicgroovy.html:title=記事書いてます]。

*2:なのになぜgroovy-2.0.x.jarやgroovy-all-2.0.x.jarが必要になるか、ですが、これらにはおそらくMethodHandleとかの「indyのための準備」に必要となるランタイムクラスが入っているためでしょう。