kagamihogeの日記

kagamihogeの日記です。

JEP 139: Enhance javac to Improve Build Speedをテキトーに訳した

http://openjdk.java.net/jeps/139 をテキトーに訳した。

sjavac(smart-javac)って何っす? ってぐぐったらこのJEPに行き着いたので読んでみた。

JEP 139: Enhance javac to Improve Build Speed

Owner    Magnus Ihse Bursie
Created 2011/09/15 20:00
Updated 2014/08/07 23:10
Type    Feature
Status  Completed
Component   tools / javac
Scope   Implementation
Discussion  compiler dash dev at openjdk dot java dot net
Effort  L
Duration    M
Priority    4
Endorsed by Brian Goetz
Release 8
Issue   8046129
Blocks  JEP 138: Autoconf-Based Build System

Summary

JDKのビルドに要求される時間を減少させます。また、インクリメンタルビルドを可能にするためにJavaコンパイラーの修正を行います。修正の内訳としては、単一の永続プロセス*1で利用可能な全コアを用いてJavaコンパイラーを実行、個々のビルド間のクラス依存性とパッケージのトラッキング、ネイティブメソッドのヘッダーファイルの自動生成、必要で無くなったクラスとヘッダーファイルのクリーンアップ、を行います。

Goals

トップレベルのゴールは以下の通りです。

  1. すべてのコアを使用するjavacとサーバープロセスのjavacを再利用することでビルド速度を向上させる。
  2. javacのビルドをインクリメンタルにすることで開発者の負担を軽減する。

このプロジェクトはJDKのビルドインフラを改善するための大規模な取り組みの一部です。

javacの改善は内部的であり、javacランチャーのpublicなAPIを通して利用可能ではありません。代わりに、smart-javac(sjavacと略される)と呼ばれる内部ラッパープログラムによって新機能が提供されます。将来的にjavacラッパーの機能が安定したら、javacのpublic APIに移動するJEPが提案されると思われます。これによってすべてのjava開発者が改善を利用可能になります。

Non-Goals

このプロジェクトではjavacに要求される変更と新しいラッパーのみ扱います。JEP 138: Autoconf-Based Build Systemを利用するJDK Makefilesで必要になる変更は対象外です。

このプロジェクトの対象外としては、javadoc生成速度向上のためのjavacの変更があります。

Success Metrics

すべてのコアを使用してjavaソースのコンパイルを行い、複数コアを使用してのビルドパフォーマンスを向上させます。

異なるコアに分散されるワークロードは完全にはバランスしません。また、同一の処理が複数回再計算されることも知られています。しかし、このJEPがサポートする変更によって、javac内のコア間の処理共有の連続的な改善が可能になります。

インクリメンタルビルドは変更のあったパッケージや依存関係のみ再コンパイルします。

インクリメンタルビルド完了後、クラスやネイティブメソッドは除去され、出力ディレクトリはクリーンされます。つまり、削除されるソースに対応するクラスやCヘッダーファイルは何も残りません。

Motivation

すべてのOpenJDKのビルドはやたらと遅く、開発者とビルドシステムに余計な負担を与えています。結果として、プロダクト全体のビルドに時間がかかり過ぎるので、開発者はソースコードの一部だけをチェックアウトしてビルドしています。

Description

内部的なsmart-javacのラッパーの実行はおおむね以下のようになります。

$ java -jar sjavac.jar -classpath ... -sourcepath ... -pkg '*' \
       -j all -h headerdir -d outputdir

上記は、sourcepathのソースファイルを、('*')にマッチするすべてのパッケージ名で、すべてのコア(-j)を使用して、コンパイルを実行します。データベースファイルが.javac_stateと呼ばれる出力ディレクトリ(-d)に作成され、これにはファストインクリメンタルコンパイルの実行に必要な情報が格納されます。また、依存性管理と、不要なCヘッダーとクラスの適切なクリーンアップを行います。

smart-javacラッパーは各コアごとにJavaCompilerインスタンスを生成することで複数コアサポートを実装します。コンパイルされるソースコードはパッケージに分割してJavaCompilersにランダムに振り分けられます。ランダムに選択されたパッケージが依存性を持つ場合、依存性は自動的にコンパイルされますが、ディスクには書き込まれません。すなわち-Ximplicit:noneです。ランダム選択されたパッケージの一部として、暗黙的にコンパイルされる依存性が後で要求される場合、その暗黙的な処理は無駄にはなりません。コンパイル済の依存性が代わりにディスクへ書き込まれます。

コンパイルの初期段階ではパッケージが依存するパッケージは不明なため、処理のランダム配分については我々が鋭意努力します。それゆえにjava.lang.Objectは存在するコア数と同じ回数分再コンパイルされますが、複数のJavaCompilersのうち一つだけがjava.lang.Objectをディスクに書き込む責任を負います。複数コアを使用する戦略を動作可能にするために、十分な数のパッケージが互いに独立しています。

将来的にjavacを拡張する(本JEPの範囲外)段階では、より多くの処理がJavaCompilers間で共有されることになるでしょう。また、最終的には、java.lang.Objectはただ一度だけコンパイルされるようにしたいと考えています。

javahはネイティブメソッドを持つ任意のクラスで自動的に実行されるようになり、生成されるCヘッダーはヘッダーディレクトリ(-h)に出力されます。新規のアノテーション@ForceNativeHeaderは、JNIにエクスポートする必要はあるがネイティブメソッドではない、finai staticなプリミティブを持つクラスで使用します。

javacのリスタートとJVMの最適化失敗を避けるために、smart-javacラッパーは-serverオプションをサポートします。このオプションはバックグラウンドjavacサーバをspawnし、それ以降のsmart-javacラッパーの実行は同じportfileを参照できるように、同じサーバを再利用します。

-server引数は、

  • portfile = 使用するTCPポートを格納するファイル。
  • logfile = javacの出力を格納するファイル、デフォルトはportfile+".logfile"
  • stdouterrfile = サーバ出力を格納するファイル、デフォルトはportfile+".stdouterr"
  • javac = サーバを立ち上げるためのjavaのパス、スペースとコンマは%20%2Cに置換。

-server:portfile=/tmp/jdk.port,javac=/usr/local/bin/java%20\
-jar%20/tmp/openjdk/langtools/dist/lib/bootstrap/sjavac.jar

現在のjavacはコンカレントコンパイルで状態を共有することは出来ないので、追加のコアは単一のjavac実行とおおむね同程度のメモリを消費します。コア間の共有の改善は本JEPの完了後に順次導入していきます。-jオプションでコア数とメモリ使用量に制限をかけられます。

javacサーバはコンパイルがすべて終了したあともメモリに留まります。サーバは、30秒間非アクティブだと、メモリとその他のリソースを解放して自動的にシャットダウンします。

サーバは通常のjavacコンパイルと同じユーザで実行され、出力ディレクトリに書き込む権限も同じです。通常のjavacコンパイルと異なるのは、コンパイルTCPポート経由でサーバに接続することで実行が可能です。コマンドラインのみで、ソースコードTCPに送信されません。

潜在的なセキュリティリスクは攻撃者が悪意のあるコードのコンパイルを追加可能な点です。リスク軽減のために、

  • portfileに格納されているポート番号のうち、毎回異なるTCPポートで開く。
  • ローカルホストからの接続のみ許可する。
  • コンパイル実行前にサーバへ提示するユニーククッキーを要求する。

クッキーは64bitランダムの整数でportfileに格納します。portfileは通常テンポラリファイルのパーミッションを持ち、オーナーのみがread/writeを行えます。

Alternatives

javacを厳密にパラレルにするのではなく、独立したjavaパッケージをパラレルに複数のシングルスレッドを使用してコンパイルすることが可能です。これはjavacへの変更は要求されないと思われますが、Makefilesを正しくすることがより困難になり、速度向上を諦めることになります。

Testing

ビルドインフラプロジェクトは旧ビルドシステムと新ビルドシステムの出力が同一であることをテストします。これはsmart-javacラッパーが同一のソースファイルから同一の出力を生成していることを確認します。

publicになる新しいオプションは無いので、javacテストスイートに追加が必要なテストはありません。しかし、smart-javacラッパー用の具体的なテストがlangtoolsテストディレクトリに追加されます。

Risks and Assumptions

新ビルドシステムが不正確な結果となる。

  • リスク: javacの変更が正しくないビルド結果を生む。
  • 緩和プラン: ビルド結果を適切にテストする。

旧と新の結果比較を単純化するために、旧makefilesは新makefileでもコンカレントに利用可能です。

Dependences

このJEPは他の変更のいずれにも依存しません。また、このJEPはJDKビルドを高速化するためにjavacの新機能を使用するJEP 138: Autoconf-Based Build Systemのための基礎部分です。新makefileはunmodified javacを使用可能ですが、このJEPが完了しない限りインクリメンタルのサポートと高速化の取り組みは行われません。

Impact

  • 互換性: インパクト低。javacに新しい引数は追加しません。
  • セキュリティ: インパクト低。descriptionの章で、コンパイルジョブ用にサーバをオープンするときのセキュリティについて説明しています。
  • I18n/L10n: 新しいsmart-javacラッパーの機能はいくつかのメッセージを追加します。smart-javacは現段階ではpublic apiとして公開されないので、完全には翻訳しない予定です。
  • テスト: ビルド自身とは切り離して、異なるsmart-javacオプションそれぞれにテストケースが書かれるべきです。

*1:原文はsingle persistent process