http://openjdk.java.net/jeps/348
JEP 348: Java Compiler Intrinsics for JDK APIs
Author Brian Goetz Owner Vicente Arturo Romero Zaldivar Type Feature Scope SE Status Candidate Component tools Discussion amber dash dev at openjdk dot java dot net Reviewed by Alex Buckley, Brian Goetz, Vicente Arturo Romero Zaldivar Endorsed by Alex Buckley Created 2018/06/25 21:23 Updated 2019/02/21 02:08 Issue 8205637
Summary
Javaコンパイラでinvokedynamicなどの代替的な変換(alternate translation)*1を使用可能にします。この目的はコンパイラintrinsic候補(compiler intrinsic candidates)に指定した特定のJDKのメソッドのパフォーマンス向上です。特に、String::format
とObjects::hash
をintrinsifyします。
Motivation
大部分において、JVM実行時のバイトコード最適化に問題はありません。しかし、ある特定のメソッドにおいて、Javaコンパイラ標準の変換は最適化困難なバイトコードになります。最たる例はString::format
で、そのシグネチャ)は以下の通りです。
public static String format(String formatString, Object... args) { ... }
javac
がString::format
に対し生成するバイトコードは、JVMのJITコンパイラが最善を尽くしても、最適化困難です。一般的なパターンはプリミティブ引数を持つ事で、これらはボクシングの必要があります。varargs配列を生成して引数で初期化します。フォーマット文字列はほとんどのケースで定数文字列ですが、String::format
を実装するたび毎回パースします。その実装は、予想出来るように、インライン化するには大きすぎます。その結果、バイトコードは、思ったよりも遥かに低速です。
String::format
とObjects::hash
(どちらも同一シグネチャ))などのメソッドは特に重要で、その理由はtoString
とhashCode
の実装を簡潔かつ堅実に行う方法だからです。開発者の中には、これらメソッドの実装を面倒くさがり、冗長で、パフォーマンス度外視でエラーになりやすい方法を使う者がいます。String::format
とObjects::hash
の最適化により、可読性とメンテナンス性が高いtoString
とhashCode
の実装方法をすると、最もパフォーマンスが出るようになります。
JEP 280はinvokedynamic
でstring concatenationを置換した結果、高速なバイトコード、アロケーションの散乱抑制、均一な最適化、となりました。我々はString::format
などのメソッドに同じやり方、代替的な変換機能でメソッド呼び出しをコンパイル、を適用します。コンパイル時に利用可能な情報、static型と呼び出し時の引数値、に基づいて特定の呼び出しに対するバイトコードをカスタマイズします。
Goals
JDK開発者に対して以下を提供します。(1)コンパイル時にintrinsificationの候補とするタグ付け機能、(2)候補メソッドの仕様に準拠するintrinsification候補の代替的な変換の適切な記述(describe appropriate alternate translations of intrinsification candidates that conform to the specification of the candidate method.)
Non-Goals
JDKライブラリ外で使えるようにintrinsification機能の公開は行わない。
Description
コンパイル時intrinsificationを行う機能には2つの特徴があります。
前者はJava SEアノテーション@IntrinsicCandidate
を作成し、JDKライブラリ作者は'候補'メソッドをintrinsificationに適するというタグ付けに使用します。これにより、コンパイラには代替的な変換を選択する余地が与えられ、振る舞いは維持したまま、変換を行います。コンパイラが行う事ではなく、コンパイラがしてもよい事、を指定するだけです。JLS 13.1はこのオプトインを許容するよう更新します。
後者をJDKのjavac
実装でするのは、intrinsic processorsの宣言および登録する機構の整備により行う予定です。このプロセッサはコンパイラがintrinsic候補の実行を検出すると呼び出され、標準の変換を最適化済みの変換に置換する方法と、置換するかどうか、をコンパイラに指示します。これらintrinsificationは任意です。コンパイラは全くintrinsifyしないかもしれないし、intrinsificationのON/OFFを指定可能なコマンドラインオプションを提供するかもしれません。
ボクシング・varargsのオーバーヘッドと定数フォーマット文字列が繰り返しアナライズされる事、を回避するため、String::format
(とPrintStream::printf
などの関連メソッド)をintrinsifyする予定です。String::format
の以下のような呼び出しを考えます。
String name = ... int age = ... String s = String.format("%s: %d", name, age);
上記のコードを実行すると、ageはボクシングしてInteger
になり、varargsにアロケートされ、nameとボクシングしたageがvarargsに入れられ、フォーマット文字列のパースと解釈、になります。これが呼び出す度に発生します。フォーマット文字列が定数の場合、コンパイル時のアナライズで以下のような代替的な変換を常に選択可能です。
String s = name + ": " + Integer.toString(age);
更にJEP 280の機能でinvokedynamic
に更に最適化が可能です。なお、代替的な変換を選択するにはnameもageもconstant variablesである必要はありません。
同様に、Objects::hash
はString::format
の3つの問題のうちボクシングとvarargsがあります。
int hashCode() { return Objects.hash(name, age); }
これもageがボクシングされ、ボクシングしたnameとageがvarargsに入ります(varargsを使うメソッドは通常防御的コピーをする)。しかし、これは以下のようにも変換が可能です。
int hashCode() { return name.hashCode() + 31 * Integer.hashCode(age); }
これにより不必要なコストを避けられます。
Risks and Assumptions
もし正しく実装されない場合、この変換が仕様やオリジナルの実装と振る舞いの互換性を完全には持てない可能性がある。
もし正しく実装されたとしても、この変換が将来的にオリジナルの実装に加えられた変更を正しくトラッキング出来ない可能性がある。
もし正しく実装しトラッキングされたとしても、intrinsic候補メソッドと変換のメンテナンスが、2箇所の変更が必要かつ振る舞いを同一に保つ事が必須なため、困難になります。
*1:標準の変換じゃなくてこっちの変換も使用可能ってニュアンスでalternateなんだが上手い単語思いつかなんででとりあえず直訳を当てている。