kagamihogeの日記

kagamihogeの日記です。

JEP 348: Java Compiler Intrinsics for JDK APIsをテキトーに訳した

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::formatObjects::hashをintrinsifyします。

Motivation

大部分において、JVM実行時のバイトコード最適化に問題はありません。しかし、ある特定のメソッドにおいて、Javaコンパイラ標準の変換は最適化困難なバイトコードになります。最たる例はString::formatで、そのシグネチャ)は以下の通りです。

public static String format(String formatString, Object... args) { ... }

javacString::formatに対し生成するバイトコードは、JVMJITコンパイラが最善を尽くしても、最適化困難です。一般的なパターンはプリミティブ引数を持つ事で、これらはボクシングの必要があります。varargs配列を生成して引数で初期化します。フォーマット文字列はほとんどのケースで定数文字列ですが、String::formatを実装するたび毎回パースします。その実装は、予想出来るように、インライン化するには大きすぎます。その結果、バイトコードは、思ったよりも遥かに低速です。

String::formatObjects::hash(どちらも同一シグネチャ))などのメソッドは特に重要で、その理由はtoStringhashCodeの実装を簡潔かつ堅実に行う方法だからです。開発者の中には、これらメソッドの実装を面倒くさがり、冗長で、パフォーマンス度外視でエラーになりやすい方法を使う者がいます。String::formatObjects::hashの最適化により、可読性とメンテナンス性が高いtoStringhashCodeの実装方法をすると、最もパフォーマンスが出るようになります。

JEP 280invokedynamicstring 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コンパイラをAuthorizing
  • 特定のintrinsification候補に対し代替的な変換を行えるように特定のJavaコンパイラで実装

前者はJava SEアノテーション@IntrinsicCandidateを作成し、JDKライブラリ作者は'候補'メソッドをintrinsificationに適するというタグ付けに使用します。これにより、コンパイラには代替的な変換を選択する余地が与えられ、振る舞いは維持したまま、変換を行います。コンパイラが行う事ではなく、コンパイラがしてもよい事、を指定するだけです。JLS 13.1はこのオプトインを許容するよう更新します。

後者をJDKjavac実装でするのは、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::hashString::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なんだが上手い単語思いつかなんででとりあえず直訳を当てている。