kagamihogeの日記

kagamihogeの日記です。

JEP 169: Value Objectsをテキトーに訳した

http://openjdk.java.net/jeps/169 のテキトーな訳。

JEPをテキトーに眺めていてValue Objectsという、おっgetter/setterがムダとかそういう話かな? と思って読んだら全然違ったという。

現行のJavaのプリミティブは64bitが最大なので、コレを越えるサイズの値を扱おうとするとアレコレ工夫が必要になる。工夫とは、ホントーは単一値にしたいものを複数の変数に分割するとか、配列にするとか、一つのクラスに複数の変数宣言して見かけ上は単一値に見せかけるとか。

ただし、やり方によっては複数の変数が連続した領域に確保されるとは限らないので、パフォーマンスが良いとは限らない。じゃあプリミティブ値だけで頑張ろうとすると、Java言語が持つ抽象化の恩恵を受けられないのでコードはゴチャゴチャになりやすい。

そんでまぁ、このJEPの解決策としてpermanently locked objectの導入を考えている。これの考え方は、ある種のvalue object(Integerとか)がある条件(immutableとか他色々ある)を備えている場合、JVM側で勝手に参照先をイジって最適化が可能、という感じ。たとえば"12345"というIntegerのオブジェクトがあるとき、もしpermanently locked objectであれば、複数の"12345"のオブジェクトがメモリ上に別の実体があろうが同一の参照があろうがどうでもよくなり、JVM側で最適化の余地が広がる(らしい)。

……といった感じで、Javaで大規模計算処理でやろうとするとブチ当たる問題と解決策を提案している……らしい。俺自身はそゆ問題扱ってるわけじゃないので、このJEPを正確に読み取れてる自信があんま無いです。なので、訳の正確性についてはお察し下さるとありがたいです。

また、これを書いてる2014/07/24の段階でUpdated 2014/07/10 20:47と、参照元が更新されてこのエントリに訳が古くなる可能性があり、Status Draftと将来的にどうなるのか不明なので、そこらへんは http://openjdk.java.net/jeps/169 をチェックして頂きたく。

JEP 169: Value Objects

Owner John Rose
Created 2012/10/22 20:00
Updated 2014/07/10 20:47
Type    Feature
Status  Draft
Component   hotspot
Scope   SE
Discussion  mlvm dash dev at openjdk dot java dot net
Effort  L
Duration    L
Priority    4
Issue   8046159

Summary

非プリミティブ型で値ベースの効率的な計算処理を支えるために、イミュータブルかつreference-free objectsと連携するためのJVMインフラを提供します。

Goals

  • Javaプリミィテブ型に準ずるパフォーマンスプロファイルを持つユーザ定義およびライブラリ定義の抽象データ型(abstract data types)サポート。
  • intjava.lang.Integerの溝を埋めるセマンティクスの提供。
  • 現在のJVMでは十分にサポートされているとは言えない普遍型(ubiquitous types)、複素数・ベクトル値・タプル、等の導入。
  • Javaデータ構造の共有性の向上(Increase shareability of Java data structures)。
  • 共有リードオンリー配列データ用の分かりやすく明確なセマンティクスの提供。
  • 最適化されたパラレル計算用の、pure dataによる関数型スタイルの導入。
  • trust boundariesを横断して構造化データを共有する必要のあるアプリケーションにおける、安全性とセキュリティの向上および"防御的コピー(defensive copying)"の削減。

Non-Goals

本JEPはJava言語仕様あるいはJVMバイトコード命令セットの変更は目的ではありません。

Motivation

Javaではプリミティブ型は合理的なパフォーマンスのためのコーディングの主要な要素です。例えば、プログラマIntegerオブジェクトのListよりもintの配列の方が安上がりだと仮定し、その通りにコーディングします。

近代的なJVMでは、オブジェクトアロケーションはそれほど高価ではなく、そのコストはout-of-line procedure呼び出し相当です。とはいえ、そのコストはしばしばプリミティブ値に対する個々の操作と比較すると耐え難いオーバーヘッドになります。このように、Javaプログラマは、(アロケーションを回避する)既存のプリミティブ型か(抽象データ型や他の有用なクラスを使用する)他の型か、どちらか一つを選ぶ事態に直面します。プログラマ複素数・ピクセル・返り値のペア・などの複数の要素からなる小規模な値を定義する必要があるとき、どちらのアプローチも目的を果たせません。このジレンマはよろしくない解決策になりがちで、その次善の策はJavaプログラムとAPIを歪めます。これは例えば、Javaで数学アルゴリズムをプログラムする人たち向けの複素数型が存在しないことを想像してみてください。

過去Javaが設計された時は今日とは異なり、現在の近代的なハードウェアは64bit以上の値の処理は日常的となっており、総称しておおむね"vectors"と呼ばれています。Javaコードで一時オブジェクトを生成せずに64bit以上の値は直接表現出来ないため、Javavector valueを動作させるのは困難を伴います。ミュータブルオブジェクト(おおむね配列)はvector valueなどのコンポーネントを保持するために生成可能ですが、ただの回避策で、値の直接的な表現とはとても言えず、その理由は同一オブジェクトが(一般的な状況では)一連の変数を連続的に保持するためです。そのコストは直接的な表現を使用したいプログラマを落胆させるには十分なものです。

コードの何箇所かにはオブジェクトアロケーションを削除可能な最適化が存在します。たとえば、エスケープ解析が正常に実行可能であれば、オブジェクトをコンポーネントのフィールドに"スカラー化(scalarized)"が可能な場合が存在します。しかし、そうした最適化は適用できる範囲と可能性が限定的です。out-of-line呼び出しでは、既存のJavaの参照セマンティクスが強制される限り、オブジェクトはメモリに束縛されなければなりません。新しい緩和された参照セマンティクスのルール無しに、ローカルのエスケープ解析はオブジェクトのボクシングオーバーヘッド削除を期待することは出来ません……どんなにプリミティブ型に近似させようと努力しても。

スカラー値 と/か(and/or) ベクトルレジスタのunboxed groupsとして自然に表現可能なJavaオブジェクトを作成でき、それらが失敗したときにはイミュータブルなboxed valueとして効率的かつ直接的に値を表現可能な、新しいルールを我々は必要としています。

Description

objectを引数に取る新しい演算子lockPermanentlyを定義し、これはそのオブジェクトがイミュータブルでunaliasableであるとマーキングします。

この演算子の使い方は見かけ上はObject.cloneに似ていますが、オリジナルのオブジェクトが新しいロックステータスで返される点が異なります。cloneと同じく、この演算子はべき等(idempotent)になります。

これはverifierに変更を要求するかもしれませんが、ロック演算子への入力方法に基づく制限を置くことが好都合になる可能性があります。

一般的には、permanently locked objectはオブジェクトの参照一意性(reference identity)に依存する任意の操作には意味のある影響を受けません。ある操作がオリジナルオブジェクトもしくはそのクローンの一つへの適用によって異なる結果になる場合、その操作は参照一意性に依存します。たとえば、

  • フィールドと配列要素が変更不可。
  • 同期化が実行不可。
  • waitもしくはnotifyするためのメソッドが呼び出し不可。
  • identity hash codeが取得不能。
  • ポインタ等価性チェック(Pointer equality checks)が実行禁止。

ポインタ等価性チェックを除いて、禁止される操作は各種の例外をスローします。pointer equality checksの制御方法が、未解決の問題に対していくつかの回答を与えます。

Permanent lockingはInteger, Booleanなどのラッパー型に適用します。標準的なプリミティブボクシングメソッドvalueOfがpermanently locked objectsを生成します。

Permanent lockingはすべての配列型に適用されます。Arrays.lockedCopyOfのようなオーバーロードメソッドが提供されるでしょう。

ロックをサポートするクラスのstatic部に制限事項が加わります。(例:全フィールドがfinalであるか、マーカーインタフェースが宣言されなければならない。)これは、ロックをサポートする値指向クラス(value-oriented classes)(Complexなど)のためのデザインパターンです。

オブジェクトがロックされると、JVMはより自由にオブジェクトのボックス・アンボックスが可能になります。特に、メソッドの引数もしくは返り値となる、任意のboxed objectに対するメソッド呼び出しで、JVMはvirtual reboxingの挿入が自由に出来るようになります。reboxing操作はある参照を同等な別の参照に置換する可能性があります。Reboxingは、完全に新しいオブジェクトのコピーか、古いオブジェクトの再利用か、どちらでも可能です。再利用はグローバルに可能で、それゆえにJVMはinterningが(必須ではないが)実行可能です。

JVMがvirtual reboxing操作の挿入を自由に出来るようになると、遅延ボクシングや、値のコンポーネントを伝える、値指向クラスを操作するメソッド用にカスタマイズされた呼び出しシーケンスが生成可能になります。なお、複数コンポーネントの値型(タプルなど)を返すことは、(マシンコードレベルでは)複数値(multiple-value)か構造体を返すかを区別できない点は注意してください。

特化した値指向呼び出しシーケンス(value-oriented calling sequences)はコンパイルされるコードに限定可能です。インタプリタ―は今までと同じように操作を続行可能です。 追加の設計事項はin the MLVM repositoryにあります。

Alternatives

現在、プログラマ複数の値を組み合わせる時には、ミュータブルな配列やバッファオブジェクトでRich Hickeyが言うところのplace-oriented programmingを使わなければなりません。引き続きこのスタイルを使い続けることは可能です。

Javaにおけるplace-oriented programmingはアンチパターンであることは強調されるべきで、その理由はJavaの変数とアプリケーションの値との対応関係が曖昧になるためです。もし、ある値が暗黙的に二つかそれ以上のJava変数に格納されると宣言する場合、単一の値・メソッド引数・帰り値として直接その変数を使う方法はありません。プログラマには、値のコンポーネントを格納するエイリアスとしての"空間(places)"だけでなく、そこに格納される値も、管理することが要求されます。最適化コンパイラには両者を区別することは困難です。

ローカルエスケープ解析は、レジスタのオブジェクトフィールドを渡すためのメソッドや、ほぼ無期限の遅延オブジェクト生成が可能な、interproceduralローカルエスケープ解析に(英雄的努力により)拡張が可能です。新しいpermanently locked objectsのためのルールが無い状況では、field mutabilityとobject identity trackingのための既存のルールは、オブジェクトのフィールド値と共にメソッド間で渡される追加のデータを要求することになります。これは複雑でおそらく実行不可能です。

我々は、クラスとプリミティブ間に第三のクリアなオプションを提供するexplicit tuple typesを、JVMに追加可能です。

配列の場合には、locking arraysを提供する代わりに、immutable array valuesを表現する新しい型を導入可能です。これはプログラマに混乱を招く恐れがあり、mutable array valuesとimmutable arrays間で余分なコピーを引き起こす可能性があります。

我々は、不変と見なせるfinalフィールドのみでアクセスされる配列を宣言する、デファクトimmutable arrays用に、既存のルール(かなりデリケートなJavaメモリモデル)を使うことも可能です。そして、配列への変更はレースコンディションになります。こうした現在のルールは、共有の可能性があるオブジェクトのfinalフィールドへ格納する前に、お定まりな配列の防御的コピーを要求します。stabilization step(often a memory fence)がより明確になる点において、この提案は優れています。また、permanently locked arrayに書き込みしようとした場合は例外をスローすることで、レースコンディションをより積極的に防ぎます。最後に、防御的コピーは本提案によって削除可能です。

permanent lockingの概念は個々のオブジェクトの代わりにクラス(もしくはその他の型)に適用可能です。実際には(Integerのような)ある種のクラスは99.99%ロックされますが、いくつかのJVM最適化を単純化すると思われます。

インスタンスごとのロックのアドバンテージは、

  • 初期化中のmutabilityについての明確化。
  • 既存のJava配列型による相互運用性の単純化。
  • ラッパーの参照指向(reference-oriented)な使用法(new Integer(n) vs. Integer.valueOf(n))の互換性。

JITは、参照指向な使われ方をするときでさえ、value objects用にコードを特殊化するために楽観的な方法を使用します。

(リードオンリーオブジェクトの初期化中のmutabilityに関する詳細な議論については、larval objectsを参照してください。)

Note: このalternativesはmethod call boundariesをまたがって(Integer同等の)複合値のアンボクシングが常に行われるメカニズムを約束するものではありません。

Testing

  • 配列操作に対する既存のテストが、ロックの正確な実行に対するテストを通過すべきである。
  • オブジェクトのフィールド操作に対する既存のテストが、ロックの正確な実行に対するテストを通過すべきである。
  • 同様に、同期化・identity hashing・ポインタ比較に対する既存のテストも通過すべきである。
  • Concurrencyのテストは、パブリッシュのためにフィールドを適切にフラッシュするオブジェクトのロックを、検証すべきである。
  • パフォーマンステストは、コンポジット値のシーケンスを生成するループが深刻なGCロードを引き起こさないことを、検証すべきである。
  • βバージョンを、ラッパー(Integerなど)値の新しい制約が既存のアプリケーションを壊さないことを確認するために、開発者とエンドユーザに提供されるべきである。
  • サンプルの値型を実装し、メカニズムの検証実験用に使われるべきである。