10 ポイント 投稿者 GN⁺ 2025-08-05 | 1件のコメント | WhatsAppで共有
  • V8エンジンで JSON.stringify 関数の性能を2倍以上高め、データ直列化の速度改善を実現
  • 副作用のないオブジェクト 向けの最適化経路を導入し、多数の防御的なチェックロジックを省略することで、一般的なデータオブジェクトで大きな高速化を達成
  • 文字列処理 では1バイト/2バイトの区別、SIMDの活用、一時バッファ構造の変更など、ハードウェアとメモリの両面で高度な手法を適用
  • 数値変換プロセス では従来のGrisu3アルゴリズムをDragonboxに置き換え、Number.toString() 呼び出し全体でも高速な変換を実現
  • 一部の引数や形式では通常の直列化経路に戻るが、ほとんどのWeb開発の場面 では自動的に最適化の恩恵を受けられる

概要

  • JSON.stringifyJavaScriptでデータを文字列に変換 する中核関数
  • この関数の性能向上は、ネットワークリクエストやlocalStorageへの保存など、Web上の非常に重要な処理にも好影響を与える
  • 最新のV8エンジニアリングにより、この機能の速度は 2倍以上改善 されており、主要な最適化手法を詳しく紹介する

副作用のないFast Path経路

  • 最適化の核心は、副作用(side-effect)のない状況 でのみ利用できる 高速な直列化経路 の適用
  • このような状況では、再帰ではなく 反復的(iterative)な構造 でオブジェクトを走査することで、スタックオーバーフロー検査が不要になり、より深いオブジェクトの直列化も試みられる
  • データオブジェクトが単純な場合、V8は遅い通常ロジックの代わりにこのFast Pathを利用し、多くのチェックを省略して高速化する

多様な文字列表現の処理

  • V8は 1バイト/2バイト文字(ASCII/非ASCII) に応じて文字列を異なる形で保存し、1つでも非ASCIIがあると全体を2バイトとして扱う
  • 文字列直列化の性能向上のため、文字列タイプごとに別個のアルゴリズム版 を作成してコンパイルしている
  • 処理中に文字列インスタンスの型を確認する必要があるため、2バイト文字列が検出されると、適切な2バイト直列化器が状態を引き継ぐ
  • その結果、文字列エンコーディングごとの経路切り替え負荷は事実上ない
  • 結果は1バイト、2バイトのバッファをそれぞれ作成し、最後に単純に結合する

SIMDによる文字列直列化の最適化

  • JavaScriptの文字列には、JSON直列化時にエスケープが必要な文字が含まれることがある
  • 長い文字列は SIMDハードウェア命令 (ARM64 Neonなど) で複数バイトを一度に検査する
  • 短い文字列は SWAR方式 により、汎用レジスタ上のビット演算で複数文字を同時に処理する
  • いずれの方式でも、ほとんどの場合は特別な変換なしに文字列全体を高速にコピーできる

Express Lane(超高速経路)の追加

  • Fast Path内でも、プロパティ検査などの反復作業なしにキーのコピーだけで直列化 できるよう、Express Laneを用意
  • オブジェクトの hidden classフラグ を活用し、キーにSymbolがなく、すべてenumerableで、エスケープ不要のまま直列化できる場合は 'fast-json-iterable' としてマークする
  • 同じhidden classを持つ別のオブジェクトを直列化する際は、追加の検査なしに すぐキーのコピーを実行 する
  • この手法は JSON.parse でも高速なキー比較に応用されている

より高速なdouble-to-stringアルゴリズム

  • 数値を文字列に変換する処理も頻度が高く、複雑さを伴う
  • 従来のGrisu3アルゴリズムをDragonboxに置き換え ることで、Number.prototype.toString() の呼び出し全体でも性能向上が得られる

一時バッファ構造の最適化

  • 文字列構築時、従来は 単一の連続バッファ を使っていたため、空きが足りなくなるたびに全体コピーが発生するオーバーヘッドがあった
  • 新しい方法では 分割(segmented)バッファ構造 を採用し、複数の小規模バッファを必要に応じて連結する
  • これにより空き不足時も、全体コピーなしで新しいバッファを割り当てるだけ で済む

制限事項

  • Fast Pathは単純なデータ直列化に限って動作する
  • 以下の条件に合致しない場合は通常経路を使用する
    • replacer または space 引数は使用不可(Pretty-Printや変形は不可)
    • toJSON カスタムメソッドを持たない単純なオブジェクトである必要がある
    • インデックスベースのプロパティ があると低速経路へ移行する
    • ConsStringなどの特殊文字列 は処理しない
  • ほとんどのデータ直列化、APIレスポンス生成、設定キャッシュなど一般的な用途では、自動的に最適化の効果が適用される

結論

  • JSON.stringify基本設計からメモリ処理、文字処理まで 全領域でアプローチを再構成し、JetStream2ベンチマーク基準で 2倍以上の高速化 を達成
  • この改善は V8バージョン13.8(Chrome 138)以降 でそのまま体験できる

1件のコメント

 
GN⁺ 2025-08-05
Hacker Newsの意見
  • JSONエンコーディングがNodeJSでプロセス間通信の大きな障害になっていると感じる

    • たいてい最終的にはイベントループの遅延を減らすために処理を別スレッドへ移そうとするが、メインスレッドのCPU負荷は結局3倍になることが分かる
    • 配列を1つずつ stringify する例も多く見かけるし、内部的にもこの方式が使われているようだ
    • V8チームにこの部分をもっと強化してほしいという期待がある
    • 一部のデータセットについて bail out なしで処理できるのか、あるいは CString 処理の問題はどうなるのか、faststr 機能が復活するのかにも関心がある
    • 昨年初めてNodeのパフォーマンス分析をしたとき、JSON.stringify はNodeサービスで性能を阻害する最大要因の1つだった
      • ディクショナリキーに stringify を使う必要があり、apollo/express はレスポンス全体を一度に文字列へ直列化してストリーミングしない
      • JVMやGoから来た立場だと、Nodeのこうした部分はかなりアマチュアっぽく感じられた
    • Pythonもまったく同じ問題を抱えている
      • 一般的なパターン向けの高水準APIの上に、効率的なIPCプリミティブがあればよいと思う
    • JSONエンコーディングが通信の大きな障害だという意見に同意する
      • 世界全体で通信におけるJSON処理の計算オーバーヘッドがどれほど大きいのか、bytes を固定フォーマットなどよりパース効率の高い方式(例: ASN.1)でそのまま送ればもっと良いのか気になる
    • V8チームがこの部分をさらに積極的に強化することには反対で、この問題を抱える開発者には別のツールを探すことを勧める
      • Node/V8はバックエンドや高性能計算の問題にはあまり向いていないと思う
      • Javascript はWeb用途に合わせて作られており、今後も長くそうであり続けるので、V8チームがこうした問題を解決する必要はない
      • Typescript チームもGoへ移行したし、言語間変換の自動化も可能だ
    • Workerに処理をオフロードして、直列化/逆直列化の時間より節約できた時間のほうが多かったケースはほぼ一度しかなかった
      • データが大きくなるほど、高コストなメッセージ受け渡しが並列化の利益と同じくらいになってしまう
  • この10年あまりで浮動小数点数の直列化性能がどれほど向上したのか、本当に驚いた

    • IEEE浮動小数点値を decimal UTF-8 文字列に変換してから再び逆変換する過程は、遅いだけでなく非常に不安定でもある
      • 2進数と10進数で正確に表現できる値が異なるため、微小な誤差が生じうる
  • JSON.stringify で replacer や space 引数があると fast path が適用されないという

    • それなら JSON.stringify(data, null, 0) を使っても fast path が可能なのか、それとも引数は undefined でなければならないのか気になる
  • SWAR escaping アルゴリズム[1]は、Folly JSONに実装されていたものと非常によく似ている[2]

  • この作業自体の価値は疑わないが、実際のV8エコシステムで JSON.stringify がランタイムを支配していた具体的な問題やデータをもっと知りたい

    • 実行時間の比率が圧倒的である必要はなく、毎日数億ページで呼び出される状況なのだから、世界的な電力削減効果はかなり大きいはずだ
  • v8の性能は十分に称賛されていないと思うし、最近のJSはものすごく速くなった

    • 本当に感嘆する、「10億ドルあればどんな問題でも解決できる」の良い例だと思う
      • 今後JSが "strict"、"stricter" のようにさらに進化して、コンパイル/JITしやすい簡潔な言語になってほしい
    • 一方で、v8はあまりにも極限まで最適化されていて、世界で本当に内部を理解している人は100人前後しかおらず、ほとんどの開発者は「なぜ自分のJSは速くないのか」と感じているだろうと思う
  • 他のエコシステムと比べて、これがどれほど優れているのか気になる

    • 10年以上JSON直列化をしてきたが、速すぎて心配したことはほとんどない
    • simdjson はコアあたり毎秒GB級の処理が可能で、プリフェッチや分岐予測などの最適化も踏まえると、JSON直列化はたいていの現実的なワークロードでは無視できるレベルだと思う
    • JSONの最大の欠点はIOオーバーヘッドであり、serializer の速度がどれだけ速くても、100MBのblobを毎回ストレージに保存しなければならないなら無意味だ
  • 「No indexed properties on objects」— fast path は通常の文字列ベースのキーを持つオブジェクトにしか最適化されておらず、array-like なインデックス属性があると slow path に戻るらしい

    • なぜなのか気になる
    • integer のように見えるキーを持つオブジェクトはJSON配列として直列化されるという意味だろうか? まさかそんなことは…?
  • segmented buffer 方式が気に入った。以前は fast-json-stringify のような userland ライブラリで rope トリックを自分で組む必要があったが、今はネイティブなのでずっと良い

    • bailout 条件(例: replacer、space、カスタム .toJSON())をどれくらい多く経験するのか気になるし、このような場合は遅い経路へ即座に fallback するのかも知りたい
  • V8は卓越しているが、JS自体のせいなのか、LuaJITやJVMより性能で劣る

    • JVMはウォームアップ時間こそ長いが、それでもなおJSより優れている
    • 原因はJSだ。V8は luajit と JVM よりはるかに進歩していると思う
      • Java はリアルタイム制約が少なく(コンパイラがある)、その点が強みだ
    • JSの多くのオーバーヘッドは動的特性によるものだ
      • asm.js はオブジェクトの shape 変更のような動的挙動を禁止することで、多くのチェックをスキップできた
    • 「JVMですら」という表現には異議がある。JVMは最高水準だ