G1GCでArrays.fillが265倍遅い理由を1命令まで追跡
Why is Arrays.fill 265 times slower on G1GC?
G1GCとParallelGCで同じArrays.fillコードを実行すると、G1GCが265倍も遅くなることが判明。その原因をアセンブリレベルで追跡し、JITが生成するコードの違いにまで特定。G1GCでは書き込みバリアが複雑で、メモリフェンスを含むスローパスが頻繁に実行されることが判明する。JDK 25、Apple M4 Max、ARM64での検証結果。
G1はParallelGCよりもはるかに多くの作業を行っていますが、同時に「何もしない」ためにずっと努力しています。
HNでの議論
24- hyperpape
この効果について純粋に興味があるのですが、AIによる文章には我慢ができません。読者を尊重した、実際の非ガベージな説明を誰かしてくれませんか?
ChatGPT無料版による少しマシな要約: https://chatgpt.com/share/6a9ac7a3-15a0-83eb-8c2a-6f72cd9beb....
自己責任でお願いします: 大まかな意味は理解できますが、詳細は考えていません。
- _old_dude_
JavaのGCはすべて世代別コレクタであり、若い世代のコレクションのマーク時間を減らすために、古い世代から新しい世代への参照があるかどうかを追跡しています。
このベンチマークでは、古い世代に配列を作成し(十分に大きいため)、新しい世代に割り当てられたオブジェクトを格納しています。これにより、すべての書き込みでGCバリアが発生します。これは実際のアプリケーションではまれなことです。
Java 26より前のG1バリアが遅い理由は以下の通りです:
- GCバリアと一部のGCスレッドが同じメモリ領域(カードテーブル)に対して並行して操作を行うため
- バリアが大きい(多くのアセンブラ命令)ため、JITによるループアンローリング最適化も妨げられる
Parallel GCは単純なバリアを持ち、レイテンシを気にしません(ループ内にGCチェックがありません)。
G1GCのバリア実装はJava 26で変更されたので、Javaランタイムを更新して先に進んでください。
- pron
ここには実用的な教訓が2つあります:
1. 最高のパフォーマンスを得るためにJDKをアップグレードする(記事にあるように、JDK 26では遅延は解消されています)。
2. オブジェクトをプールしてGCを助けようとしないこと。古いオブジェクトを変更するのは高くつく可能性がありますが、新しいオブジェクトを割り当てるのは安価です(特に、非常に高価な初期化を行わないオブジェクトの場合)。