GraalVM で google-java-format-cli を高速化する

こんにちは、サーバーで開発している持田です。

現在、弊社のサーバープログラムは Java で記述されており、そのコードフォーマッターに google-java-format を使っています。 このエントリーではそのツールである google-java-format-cli を graalVM で高速化する方法ついて紹介します。


google-java-format-cli の利用

今年の春頃から弊社の Java コードはすべて google-java-format で統一されております。 google-java-format は gradle プラグインも有志により提供されており、弊社のビルド環境にもマッチしております。

しかし、 gradle プラグインによる google-java-format の適用は当初は問題がないように思えていたのですが、 gradle の起動に時間がかかってしまうことがそのうちに不満になってきました。

そこで弊社の阿部が google-java-format-cli を GraalVM でネイティブ化して利用する技を編み出しました。

graalVM を使ってネイティブ化した cli を作る方法

GraalVM のダウンロード

まず、 GraalVM をダウンロードします。2018/11/22 現在ですと、 sdkman を利用して GraalVM をダウンロードするのが 最も手軽な方法ではないかと思います。

GraalVM は graalVM という Candidate が sdkman にあるわけではなく、 Java の一つのバージョンとして登録されています。

~ $ sdk l java
================================================================================
Available Java Versions
================================================================================
     12.ea.17-open     + 7u141-zulu                                             
     11.0.1-zulu         7.0.181-zulu                                           
     11.0.1-open     > * 1.0.0-rc9-graal                                        
   + 11.0.0-open         1.0.0-rc8-graal                                        
     10.0.2-zulu         1.0.0-rc7-graal                                        
     10.0.2-open       + 1.0.0-rc5-graal                                        
   + 10.0.1-zulu                                                                
   + 10.0.1-oracle                                                              
   * 9.0.7-zulu                                                                 
     9.0.4-open                                                                 
   + 9.0.1-oracle                                                               
   + 9.0.0-zulu                                                                 
   + 8u144-zulu                                                                 
     8.0.192-zulu                                                               
     8.0.191-oracle                                                             

================================================================================
+ - local version
* - installed
> - currently in use
================================================================================

2018/11/28 時点で sdkman から入手できる GraaalVM は rc9 が最新ですので、こちらをダウンロードします。

sdk i java 1.0.0-rc9-graal

ダウンロード完了後、こちらを有効にします。

sdk u java 1.0.0-rc9-graal

GraalVM が使えるようになると native-image というコマンドが使えるようになるので、確認します。

~ $ which native-image
/Users/user/.sdkman/candidates/java/1.0.0-rc9-graal/bin/native-image

google-java-format-cli のダウンロード

google-java-format の GitHub のページ の リリースから最新の all-deps という jar ファイルをダウンロードします。

2018/11/22 現在の最新版は 1.6 ですので、こちらをダウンロードします。

curl -L -O https://github.com/google/google-java-format/releases/download/google-java-format-1.6/google-java-format-1.6-all-deps.jar

native-image で google-java-format をネイティブ化

native-image コマンドにダウンロードした google-java-format のネイティブバイナリーを作らせます。

なお、 google-java-format-cli は org.openjdk.tools.javac.resources.compiler というリソースバンドルを必要とするので ネイティブバイナリーにそれが含まれるようにオプションを設定します。

native-image \
    -H:IncludeResourceBundles=org.openjdk.tools.javac.resources.compiler \
    -jar google-java-format-1.6-all-deps.jar

次のようなログが出力されて、 google-java-format-1.6-all-deps という実行ファイルができあがります。

Build on Server(pid: 76055, port: 55909)
[google-java-format-1.6-all-deps:76055]    classlist:   3,051.22 ms
[google-java-format-1.6-all-deps:76055]        (cap):   2,655.40 ms
[google-java-format-1.6-all-deps:76055]        setup:   8,985.18 ms
[google-java-format-1.6-all-deps:76055]   (typeflow):  26,461.53 ms
[google-java-format-1.6-all-deps:76055]    (objects):  11,403.33 ms
[google-java-format-1.6-all-deps:76055]   (features):     538.06 ms
[google-java-format-1.6-all-deps:76055]     analysis:  54,496.30 ms
[google-java-format-1.6-all-deps:76055]     universe:   1,930.00 ms
[google-java-format-1.6-all-deps:76055]      (parse):   8,009.36 ms
[google-java-format-1.6-all-deps:76055]     (inline):   7,740.39 ms
[google-java-format-1.6-all-deps:76055]    (compile):  98,381.88 ms
[google-java-format-1.6-all-deps:76055]      compile: 133,017.12 ms
[google-java-format-1.6-all-deps:76055]        image:   3,771.10 ms
[google-java-format-1.6-all-deps:76055]        write:   1,626.74 ms
[google-java-format-1.6-all-deps:76055]      [total]: 242,031.33 ms

ネイティブ化された google-java-format-cli を利用する

早速、ためしてみます。次のインデントも何もバラバラな Foo.java ファイルを用意します。

public class Foo {
public static void main(String... args) {final String value = System.getProperty("foo");


    System.out.println(value);    }  }

これに対して次のコマンドを実行すると、 Foo.java ファイルに google-java-format が適用されます。

./google-java-format-1.6-all-deps --replace Foo.java

適用後のファイル

public class Foo {
  public static void main(String... args) {
    final String value = System.getProperty("foo");

    System.out.println(value);
  }
}

計測

先程の Foo.java のフォーマットについて、ネイティブ化する前と後とで実行時間を計測してみます。

jar ファイルを java コマンドで実行した場合
~ $ time java -jar google-java-format-1.6-all-deps.jar --replace Foo.java

real    0m1.209s
user    0m1.632s
sys     0m0.333s
ネイティブ化した google-java-format-cli を実行した場合
$ time ~/tools/google-java-format-1.6-all-deps --replace Foo.java 

real    0m0.039s
user    0m0.011s
sys     0m0.017s

実行時間がかなり短縮されたのがわかるかと思います。

実際に使う場合には次のように git で更新のあったファイルを取り出して適用する感じで使っています。

git status --short --branch | \
  grep java | awk '{print $2"\n"$4}' | \
  grep java | xargs ls -d 2>/dev/null | \
  xargs ~/tools/google-java-format-1.6-all-deps --replace

以上、 graalVM を活用した google-java-format-cli の高速化について紹介しました。 今回のような Java で書かれたちょっとしたツールであればネイティブ化すると高速化できるかもしれません(必ずしも速くできると保証はしません)。 みなさんの普段行っている業務でも試してみてください。

なお、 graalVM はまだサポートされていないフィーチャーがあるため、まだ Java で書かれたツールをなんでもネイティブ化できるわけではないようです。 たとえば plantuml をネイティブ化しようとしたところ、 2018/11/28 の時点でサポートされていないフィーチャーがあるためにネイティブ化できませんでした(残念)。 このあたりについては今後に期待しましょう。

L is B では現在 Java を書きたいサーバーサイドエンジニアを募集しています。こちらまでご応募・ご連絡ください。

タイトルとURLをコピーしました