PaPoo
cover
technews
Author
technews
世界の技術ニュースをリアルタイムでキャッチし、日本語でわかりやすく発信。AI・半導体・スタートアップから規制動向まで、グローバルテックシーンの「今」をお届けします。

JDK 28 にやってくる Valhalla、10年越しの「速さ」と「わかりやすさ」の折り合い

Javaの世界で長年ほぼ伝説扱いだった Project Valhalla が、ついに JDK 28 に入ってくる見通しになりました。元記事は、このニュースを起点に「そもそも Valhalla って何なのか」「なぜこんなに時間がかかったのか」「JDK 28 で本当に何が入るのか」を、かなり丁寧にたどっています。

個人的に面白いと思ったのは、Valhalla が単なる“高速化プロジェクト”ではないことです。もっと本質的には、​Java の書きやすさ機械にとっての扱いやすさが、ずっとズレたままだったのを何とか揃えようとしている話なんですね。ここ、地味だけどかなり大きい。

まず押さえたいポイント

Java は、どうしてこんなに「重い」のか

image_0005.png

Valhalla の話を理解するには、Java の基本に少し戻る必要があります。Java では primitive 型以外、つまり intboolean 以外のものは基本的に reference type です。たとえば Point p = new Point(1, 2) と書いたとき、p 自体が点そのものではありません。実体はヒープというメモリ上のどこかに置かれていて、変数 p はそこへの 参照 を持っているだけです。

これが普通に見えるのは、日常のコードでは問題になりにくいからです。でも、数が増えると話が変わります。参照をたどって実体を取りに行く構造は、メモリ上でデータが散らばりやすい。たくさんの Point があっても、実際には「点の配列」ではなく「点への住所の配列」になりがちです。

ここで効いてくるのが CPU の cache です。CPU は一個ずつメモリを取りに行くのではなく、ある程度まとまった塊を読みます。データがきれいに並んでいれば、その塊の中に必要な情報がたくさん入っていて速い。逆に、あちこちに散っていると cache miss が増えて遅くなる。

元記事が言う「fluffy」という表現はなかなか言い得て妙で、​ふわっと膨らんだ、密度の低いメモリ配置を指しています。人間から見ると同じ Point の集まりでも、マシンから見ると全然違う、という話です。こういうところ、Java は長年ずっと“親切な顔をした重さ”を抱えていたんだな、と感じます。

じゃあ escape analysis でいいのでは、となるが……

image_0006.png

ここで「でも JVM には escape analysis があるじゃないか」と言いたくなります。これは、あるオブジェクトがローカルな処理の外に漏れないなら、実際にはヒープに作らずに済ませる最適化です。うまくいけば allocation も GC もかなり減ります。

ただ、元記事が強調している通り、これを土台にしてしまうのは危ない。というのも、最適化の成否が JIT compiler の判断にかなり左右されるからです。ちょっとコードの形を変えるだけで、JIT が追えなくなって効果が消えることがある。JDK のバージョンを変えたら挙動が変わることもある。

性能がそんなに不安定だと、運用する側は怖いです。個人的にはここが Valhalla の本質のひとつだと思っています。
「速ければいい」ではなく、​速さがたまたま出るのではなく、構造として速さを得たい。Valhalla はそこを狙っている。

手で並べれば速い。でも、今度は人間がつらい

じゃあ最初から object を使わず、r, g, b のように primitive で持てばいいのでは、という発想もあります。実際、ゲーム、画像処理、データベース、HPC みたいな世界では、こういう“手書きの詰め方”が昔から使われてきました。速いからです。

image_0008.gif

ただし、これはかなり危うい。
たとえば色の順番を RGB だと思っていたら実は BGR だった、みたいなミスは普通に起きます。型の意味が消えるので、Color という名前が持っていた安全性、検証、メソッド、private state のような恩恵も失われる。

ここが Java らしいジレンマです。
書きやすい型は遅くなりがち。速い表現は危なっかしい。​
Valhalla はその二択を崩したいわけです。

10年かかったのは、単に難しかったから

Valhalla は 2014 年に正式に始まったとされますが、考え自体はもっと古い。Java の初期から value types 的なものは欲しかったけれど、1995 年時点では難しすぎて見送られた、という話が出てきます。

しかもこのプロジェクト、最初から一本道ではありませんでした。記事では、なんと 5つの prototype を経たと説明されています。これが面白いところで、Valhalla は「着実に積み上がった」というより、​何度も設計を壊しては作り直した歴史なんですね。

image_0009.png

最初期の発想は、いわば Q World 的な方向でした。新しい型を primitive と同じくらい別物として扱い、型記述子や bytecode も分けようとした。理屈としてはきれいですが、JVM 全体が二重化してしまい、複雑さが爆発する。

そこで後に L World が出てきます。こちらは、value types を ordinary reference と同じ “L carrier” に乗せる発想です。驚くべきことに、この統合はうまくいった。しかも、前段階で抱えていた問題の多くも一緒に片付いた。

このあたり、技術の進化って本当におもしろいです。
「もっと厳密に分けたほうが理論的には美しい」ことが、必ずしも現実の設計では正解じゃない。むしろ、​レイヤーを分けるほうがよかった。言語モデルと JVM モデルは完全一致しなくていい、という気づきが大きかったのだと思います。

名前がコロコロ変わったのも、話が難しかったから

image_0010.png

Valhalla を追ってきた人が混乱しやすい理由のひとつが、名前の変遷です。記事では、これをかなり丁寧に整理しています。

最初は value types。まだ何者かが曖昧でした。
その後、​inline classes という考え方が固まり、identity を持つ class と、identity を持たない class を分ける方向になります。final で、field も finalsynchronized できない、といった制約がここで見えてくる。

さらに一時期は primitive classes と、その 二重の見え方 まで考えていました。ひとつの型に value 版と reference 版の2面を持たせる、という発想です。Point.val / Point.ref のような表現や、Point! / Point? のような記法も試されました。

これは強力だけど、正直かなり重い。
開発者が日常的に「今触っているのはどっちの顔だっけ?」を意識しないといけないのは、かなりつらいはずです。元記事の言い方を借りるなら、​モデルとして強いが、頭の中で抱えるコストも高い
最終的には、この二重性をかなり削った方向に落ち着いています。これは良い判断だと思います。強い機能ほど、使う人の認知負荷が低くないと、結局みんな使わなくなるので。

JDK 28 で入るのは「完成形」ではない

image_0011.png

今の JEP 401 は、value modifier を使って value class を宣言できるようにするものです。そして、そのインスタンスは value object と呼ばれます。ポイントは、value class 自体はまだ reference type だということ。ここ、直感に反しやすいので注意が必要です。

つまり「値っぽい振る舞いをするクラス」が入るのであって、いきなり全部が primitive 化するわけではありません。さらに、null を許さない型の話は別の JEP に切り出されました。記事では Null-Restricted Value Class Types として触れられています。

この分離はかなり重要です。
一気に全部やると、概念が濁る。
「値として扱うこと」と「null を持てないこと」は似ているようで別問題なんですね。設計を分けたことで、少なくとも理解しやすさは増したと思います。

元記事でも Brian Goetz の言葉が紹介されていて、Valhalla に対して「まだ最初の部分にすぎない」と釘を刺しています。つまり、今回の JDK 28 はゴールではなく、長い旅の本当に序盤です。

これで何が変わるのか

image_0012.png

一番わかりやすい変化は、​普通の class っぽい書き方をしながら、内部ではもっと密にデータを並べられる可能性が出てくることです。配列や大量データを扱う場面で、これが効いてくるはずです。

ただし、万能薬ではありません。
まず preview ですし、しかも disabled by default です。つまり、すぐに全部のコードベースで置き換えられる種類の話ではない。さらに、Valhalla はこれで終わりではなく、今回の段階はあくまで “first part” です。

だから、今の時点で「Java がついに全部速くなる」と期待すると肩透かしを食らいます。むしろ本当に見るべきなのは、​Java が性能のために自前の罠だらけの表現を使わなくて済む方向に進み始めたことだと思います。これは地味だけど、かなり大きな転換です。

たぶん一番おもしろいのは、思想の変化

この記事を読んで強く感じたのは、Valhalla は単なる機能追加ではなく、Java の哲学を少し組み替える仕事だということです。

image_0013.png

昔の Java は「安全で書きやすいこと」をかなり優先してきました。その代わり、速度の面では primitive と object の差をある程度受け入れていた。Valhalla はその前提を崩しにきています。
しかも、無理やり全部を一種類に統一するのではなく、​参照の世界と値の世界をうまく同居させる方向で。

このバランス感覚が、いかにも Java らしいなと思います。派手な革命ではないけれど、長く効く変更です。JDK 28 の Valhalla は「ついに来た」と言いたくなる一方で、実際にはまだ入口です。でも、その入口が開いた意味はかなり大きい。ここから Java のデータ表現は、少しずつ別の景色になっていくのではないかと思います。


参考: Project Valhalla, Explained: How a Decade of Work Arrives in JDK 28 - JVM Weekly vol. 180

同じ著者の記事