概要
- Java最適化の現場で、分散データ処理プラットフォームのパフォーマンス向上に取り組んだ経験談
- VarIntエンコーディングのCPUコストに着目し、SIMDアセンブリによる高速化を実施
- ベンチマークでは大きな高速化を確認したが、実運用では効果が現れなかった理由の発見
- ランダムな値と実際のデータ分布の違いが、最適化評価に大きく影響する事例
- 最終的に変更をロールバックし、JIT最適化の知見を得たエピソード
Java最適化現場の挑戦とVarIntエンコーディング
- 分散データ処理プラットフォーム開発現場での経験
- 数十万台規模のシステムで 0.5%の改善 でも大きな価値
- 既に徹底的に最適化された Javaコードベース
- java.lang.String の最小限利用、GC負荷軽減策
- さらなる最適化余地を求めた プロファイリング
- データシリアライズ、特に VarInt(ULEB128)エンコーディング に着目
VarInt(ULEB128)エンコーディングの仕組み
- 任意精度整数 を効率的にエンコードする方式
- 7ビット単位で数値を分割し、8ビット目で継続判定
- 1バイトで7ビット未満、2バイトで14ビット未満 など、リトルエンディアン順
- 32bit intは 1~5バイト、64bit longは 1~10バイト
- Google Protobuf, Apache/Facebook Thrift, WASM, DWARF などで採用
超最適化VarIntエンコーダの開発
- encodeLong(byte[] buffer, int offset, long data) :バイト数を返すインターフェース
- 通常はネイティブコード化のメリットが小さいとされる領域
- JVMのフォーク、独自最適化インフラ を活用
- JIT直列化、JNIオーバーヘッド回避
- SIMDアセンブリ(BMI2, AVX2) による分岐なし高速化
- 既存Java実装の4倍のパフォーマンス をベンチマークで確認
- 数十億件のランダム値でテスト、互換性も検証
- 実装、テスト、統合、リリースまで2週間
ベンチマークと実運用のギャップ
- 本番環境での効果測定: 差分ゼロ
- ランダム値ベンチマークの落とし穴
- 64bitランダム値 では大半が 9~10バイト 必要
- 実際のデータはほとんどが 2バイト以下 で表現可能
- VarIntの設計意図: 小さい数字が多い現実
- Java実装は出力バイト数が増えるほど負荷増
- 最適化効果が現れたのは 最悪ケースのみ だった事実
- 現実的な分布 で再ベンチマーク→高速化効果消失
結論と学び
- 変更をロールバックし、 JIT最適化の知見 を蓄積
- SIMD最適化 の実践経験を得る
- ベンチマーク設計の重要性、実データ分布の理解
- 最適化は 現実的なユースケース を想定することが不可欠