こんにちは、サーバーで開発している持田です。
現在、弊社のサーバープログラムは 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 を書きたいサーバーサイドエンジニアを募集しています。こちらまでご応募・ご連絡ください。