この動画は、ChromeのV8エンジンにおけるJSON.stringifyの大幅なパフォーマンス向上について解説している。従来の2倍から3倍の高速化を実現した技術的な最適化の詳細を、副作用のない高速パスの実装、SIMD命令を使った文字列処理、隠れクラスを活用した効率化、メモリ管理の改善など、低レベルの実装から分かりやすく説明する内容である。

- JavaScriptの速度とJSONの可能性
- 技術的な深堀りと面白い事実
- スポンサー紹介:Augment Code
- JSON.stringifyの最適化について
- 呪われた使用例
- 副作用のない高速パス
- シリアライズ深度の恐怖
- 異なる文字列表現の処理
- ConStringとは
- 具体例でのガベージコレクション
- より深い例
- エンコーディングの切り替え
- SIMD による文字列シリアライズの最適化
- V8のコード行数
- 高速レーンの実装
- V8の隠れクラス
- オブジェクト形状の具体例
- アヒルの例で隠れクラスを説明
- JSONパースの方が速い場合
- 最適化の注意点
- 新しい最適化に戻る – より速いdouble to string アルゴリズム
- asm.jsの思い出
- さらなる最適化 – 基礎となる一時バッファの最適化
- セキュリティ上の懸念
- GPT-5での実験
- 制限事項
- オブジェクトでの制限
- GPT-5の予測結果
- 結果
- まとめ
JavaScriptの速度とJSONの可能性
JavaScriptは速い言語やないのは、みんな知っとることやと思う。せやけど、JSONがどんだけ速いかは驚くかもしれへん。特にJSON.parseとstringifyや。歴史的に見ても、JSONを実際のオブジェクトに変換するのは最高の実装の一つやった。なんでかって?そらJavaScript Object Notationやからな。
全体のポイントは、シリアライズされたJavaScriptオブジェクトやということや。当然、相互変換できるわけや。せやけど、もしこれが2倍速くなったって言うたらどうや?まあ、ほんまは嘘になるな。なんでかっていうと、多くのシナリオで実際は3倍近く速くなってるからや。Chromeチームが JSON.stringify をさらに速くするためにやってくれた仕事に、ワイは完全にぶっ飛んでる。
技術的な深堀りと面白い事実
これは小さくて単純に見えるかもしれへんけど、実際は掘り下げる楽しいネタがぎょうさんある。JSON.stringifyの実装から、この手の処理でのV8のパフォーマンスの奇妙なクセ、そしてワイの大好きな豆知識の一つまで。多くのオブジェクトは、直接オブジェクトを定義するよりも、JSONとして定義してからJSON.parseする方が、VMでの読み込みが速くなる場合があるっちゅうことや。
ここには掘り下げる楽しいことがぎょうさんある。人々は、毎日頼りにしてるこういう単純なもんを、大多数の使用例で速くするためにJSONにどんだけのことが詰め込まれてるか、あんまり評価してへんと思う。この変更みたいに、ワイは動画を副作用フリーに保ちたいけど、もっと重要なのは一般的に無料に保ちたいっちゅうことや。
誰かが費用を負担せなあかん。そやから、今日のスポンサーから手短に一言もろて、それから掘り下げていこう。
スポンサー紹介:Augment Code
AIを使ってアプリケーションを構築するのは、今まで以上に簡単になった。小さなアプリケーションと小さなコードベースを構築してる限りはな。でかいもんをやろうとすると、すぐに破綻し始める。それが今日のスポンサーのAugment Codeがめっちゃクールな理由や。
こいつらは巨大なコンテキストの管理方法を見つけ出してる。君が実際の仕事で取り組んでるようなやつをな。大企業がどんどん移行し始めてて、ワイ自身もこれを見てきた。なんでかっていうと、Augmentとは数回しか広告を出してへんのに、彼らは定期的にみんながいちばん気に入ったスポンサーリストのナンバーワンになってるからや。彼らに対するセンチメントがどんだけポジティブかが信じられへんかった。使ってみるまではな。そしたら意味がわかった。めちゃくちゃすごいからや。ほんまにええ。
彼らは最近バックグラウンドエージェントを導入して、クラウドでプルリクエストを開いてくれる。ワイがこの手のもんで他に試したやつは、あんまり信頼してへん。なんでかっていうと、自分のマシンで実行できへんのやったら、どこにも行かへんと信頼してへんからや。せやけど、彼らは他のどのツールとも違って、コードベース全体のコンテキストを持ってるから、正しくやる可能性がずっと高い。
これはSWEBenchをぶっ潰したことでわかる。今日までで最高のスコアや。彼らはコードでの難しい問題を解決するのがほんまにええもんを作った。Uber、Lemonade、Vercel、その他の企業が今日Augmentを使ってる。正直、ワイ自身もこれらの巨大なコードベースで何かを見つけるのにもっと使うようになってる。
大きなオープンソースのコードベースで何かがどう動いてるかを理解したいとき、これがワイのやり方や。ホームページの体験談がすべてを物語ってる。「Augmentは独自のクラスや。他の拡張機能は全然近づけへん。信頼性、インデックス化、メモリ、君のコードベースをほんまによう知ってる。永遠に使い続けたい。Augmentのすべての機能は、実際に大規模でソフトウェアを出荷する人たちによって作られた感じがする」
そやな、もしバイブコーディングをやってるんやったら、これはパスしてもええ。せやけど、ほんまのソフトウェアを構築してるんやったら、今日チェックしてみいや。sov.link/ogment で。
JSON.stringifyの最適化について
おお、ワイはこの変更にほんまに興奮してる。JSONをいろんな場所で使ってるから、これはめっちゃ楽しいはずや。JSON.stringifyは、データをシリアライズするためのコアJavaScript関数や。他に何に使うかは聞かへんで。答えがあるんやったら教えてくれ。ワイの日を台無しにしてくれ。
このパフォーマンスは、Web全体の一般的な操作に直接影響する。ネットワークリクエスト用のデータのシリアライズからローカルストレージへのデータ保存まで、JSON.stringifyの高速化は、よりクイックなページインタラクションとよりレスポンシブなアプリケーションに変換される。
そやから、最近のエンジニアリング作業がV8のJSON.stringifyを2倍以上速くしたことを共有するのに興奮してる。この投稿は、この改善を可能にした技術的最適化を分解してる。
呪われた使用例
おお、呪われた使用例が出てきた。これはPorfurerの作成者からや。JSONとJavaScriptの魔法使いや。オブジェクトのクローン。JSON.parse(JSON.stringify(obj))。うわ。これは深いクローンの一般的なパターンや。うわ、それは痛い。
覚えといてや、ワイらは副作用のない高速パスが欲しいねん。ほんまに速くて奇妙な副作用を起こさへんもんが欲しい。
副作用のない高速パス
この最適化の基盤は、単純な前提に基づいて構築された新しい高速パスや。オブジェクトのシリアライズが副作用を引き起こさへんことを保証できるんやったら、ずっと速い特殊化された実装を使えるっちゅうことや。
この文脈での副作用っちゅうのは、オブジェクトの単純で合理化された走査を壊すあらゆるもんや。これにはシリアライズ中にユーザー定義コードを実行するような明らかなケースだけやなく、ガベージコレクションサイクルを引き起こす可能性のあるより微妙な内部操作も含まれる。
副作用を正確に引き起こす可能性のあるもんと、それをどう回避できるかの詳細については、制限事項を参照してくれ。
ワイはいっぱい考えがある。V8でのガベージコレクション、前の動画で見たかもしれへんけど、作者が間違ってたことをワイが偶然正しく推測して、修正を発行してもらわなあかんかった。それは楽しかった。今回これがワイをどこに連れて行くか見てみよう。これは普通行くよりも深い草むらにあって、ワイがアホに見えるかもしれへんけど、見つける方法は一つしかない。
V8がシリアライズがこれらの副作用から解放されることを決定できる限り、高度に最適化されたパスにとどまることができる。これにより、汎用シリアライザーに必要やった多くの高価なチェックと防御ロジックを迂回できて、プレーンなデータを表す最も一般的なJavaScriptオブジェクトの種類に対して大幅な高速化をもたらす。
さらに、新しい高速パスは再帰的な汎用シリアライザーとは対照的に反復的や。このアーキテクチャの選択は、スタックオーバーフローのチェックの必要性を排除するだけやなく、エンコーディング変更後のクイックな再開を可能にするけど、開発者が以前可能やったよりもずっと深くネストされたオブジェクトグラフをシリアライズすることも可能にする。
シリアライズ深度の恐怖
おおいや。おおいや。もしシリアライズの深度制限がどんなもんかって怖いな。Canada honkが答えをくれるやろう。V8で通常問題が起こる前に、オブジェクトをどれだけ深くシリアライズできるか?泣いてる。それがワイが聞く必要があった全部や。それがワイが聞く必要があった全部や。本当の答えは必要ない。もし持ってるんやったら教えてくれ。せやけどワイが探してた情報は手に入った。
異なる文字列表現の処理
V8の文字列は1バイトまたは2バイト文字のどちらかで表現できる。うわあ。文字列がASCII文字のみを含む場合、V8では1文字あたり1バイトを使用する1バイト文字列として保存される。しかし、文字列がASCII範囲外の文字を一つでも含む場合、文字列のすべての文字が代わりに2バイト表現を使用するようになって、本質的にメモリ使用量が2倍になる。
ワイはひどいことをした。これで善良なOliverがV8のソースを見に行かせてしまった。ほんまにごめん。これは楽しい大きな言葉でいっぱいや。
統合実装の一定の分岐と型チェックを避けるために、文字列化器全体が文字型にテンプレート化された。つまり、シリアライザーの2つの異なる特殊化されたバージョンをコンパイルするっちゅうことや。一つは1バイト文字列に完全に最適化されたもんで、もう一つは2バイト文字列に完全に最適化されたもんや。
これはバイナリサイズに影響があるけど、パフォーマンスの向上は絶対に価値があると思ってる。それはめっちゃ興味深い。なんでかっていうと、もしJSONに絵文字が入ってるんやったら、1バイト文字列の最適化は助けにならへん。そやから、代わりに2回コンパイルした。そやから、シリアライザーの2つの完全な実装がある。特殊文字がない場合の一つと特殊文字がある場合の一つや。それはちょっと面白い。
JavaScriptデベロッパーはどんな問題にも解決策をインストールするやろ?実装は混合エンコーディングを効率的に処理する。シリアライズ中、高速パスで処理できへん表現を検出するために、各文字列のインスタンス型をすでに検査せなあかん。
ConStringとは
ConStringって何?ConStringクラスは、文字列の加算演算子を使って構築された文字列値を記述する。ああ、面白い。ランダムに文字列を一緒に追加できる。そうすると、ConStringができる。これは構築された文字列やと仮定してて、それらは副作用を持つ可能性がある。
そやからシリアライズ中、高速パスで処理できへん表現かどうかを検出するために、各文字列のインスタンス型をすでに検査せなあかん。なんでかっていうと、ConStringはフラット化中にガベージコレクションを引き起こす可能性があるからや。もう必要やない前の値や、新しい文字列でメモリ制限を超えた場合、ガベージコレクションが入ってきて、以前使用されてへんかったメモリや、新しい値を定義したから使用されへんようになったメモリをクリーンアップせなあかん。
具体例でのガベージコレクション
ワイらが話してることの簡単な例として、greet関数がある。greet関数は、すでに定義した空の文字列と、渡す名前を使う。そして、ワイの購読者の一人を迎える。ちなみに、まだ購読してへんかったら、何も費用はかからへん。ボタンは動画の真下にある。クリックしてくれ。助かる。
console.logの代わりに、これをmessageとして定義すると想像してみい。そしてconsole.log messageや。ここでワイについてきてくれ。古いiPhoneみたいなもん以外のほとんどのコンピューターは、このテキストコンテンツ全部を完全に問題なく収めるのに十分なメモリを持ってるのはわかってる。せやけど、仮にこれらを一緒に追加したときにメモリが足りなくなったとしよう。
これらを追加してmessageを作成したとき、メモリ制限を超えてる。これはガベージコレクションを引き起こして、V8エンジンがもう参照されてへんすべてのもんを探して、参照を外すことになる。使用されてへん他の値や、ヒットされてへん定義を落とすことができる。
nameが存在するスコープにまだいるから、この文字列がクリーンアップされるほど賢いとは思えへんけど、仮定的にはできる可能性がある。ここでのポイントは、この新しい文字列の作成が、特に消費時に、なんでかっていうとこれらは実際は遅延評価されることをワイは今学んだからや。そやから、定義するときは消費されへん。使用するときに評価されて、その時点でガベージコレクションが発生する可能性がある。
より深い例
そやから、もう一歩進もう。実際、message を返して、const someMessage equals greet(一人の購読者)この時点で。そして、繰り返すけど、ワイが間違ってるんやったら訂正してくれ、Canada honkが、チャットで君を見かける。めっちゃ助かってる。この追加はまだ実際に計算されてへん可能性がある。
そやから、この時点で、console.log someMessage。これで計算が完了する。文字列が一緒に絞り込まれる。そして、ここで渡した名前はもうこのコードがすでに実行されたから関係ない。そやからこれは今クリーンアップのためにマークされる。
そやから、これを実際に使うとき、またはワイらが気にするケースでは、const JSONified equals JSON.stringify(someMessage)、これが追加が実際に起こるポイントや。このファイルの他のもんで、この時点でもう関係ないもんがある。この文字列はもうここで追加されて加算されて計算されてるから、もう気にする必要がない。それは遅延実行される。
せやけどもっと重要なのは、遅延実行されるとき、もうどうでもええもんをクリーンアップする機会があるっちゅうことや。そやから、このJSON.stringifyを呼び出すとき、まだメモリにあるかもしれへんこの文字列は、もうある必要がない。せやけどそれも副作用の一種で、パースするとき、この文字列化をするときに気をつけなあかん。
もし副作用があるんやったら、この場合はあるけど、ちょっと違った扱いをせなあかん。助けてくれてありがとう。一般的な用語はロープ文字列や。知っとくのはめっちゃええ。そして、これがガベージコレクションを引き起こすかどうかを判断するためにやるチェックは、1バイトか2バイトエンコーディングを使う必要があるかもチェックする。便利な一石二鳥や。
エンコーディングの切り替え
このため、楽観的な1バイト文字列化器から2バイト版への切り替えの決定は、本質的に無料や。この既存のチェックが2バイト文字列を明らかにしたとき、新しい2バイト文字列化器が作成されて、現在の状態を継承する。最後に、最終結果は初期の1バイト文字列化器の出力と2バイトのもんの出力を単純に連結することで構築される。
ああ、そやから彼らもConStringを作ってる。この戦略は、一般的なケースで高度に最適化されたパスにとどまりながら、2バイト文字を処理する移行が軽量で効率的であることを保証する。
そしてCanadaは今テストを完了した。Oliverによると、V8が壊れるまでJSON.stringifyで4000の深度まで到達できた。制限が今高くなってるのを知るのは怖い。なんでかっていうと、その制限にぶつかってるんやったら、何か悪いことをしてる。そして今、もっと長く悪いことができる。
SIMD による文字列シリアライズの最適化
最適化に戻る。SIMD による文字列シリアライズの最適化。JSのどの文字列も、JSONにシリアライズするときにエスケープを必要とする文字を含むことができる。クォートやバックティックみたいなもんや。それらを見つけるための従来の文字ごとのループは遅い。控えめに言っても、遅いやろう。
これを加速するために、文字列の長さに基づく2レベルの戦略を採用してる。長い文字列については、ARM64みたいな専用ハードウェアSIMD命令に切り替える。これにより、文字列のずっと大きなチャンクを幅広いSIMDレジスターに読み込んで、エスケープ文字があるかどうかを複数バイト一度に、わずか数命令でチェックできる。
そうや、彼らはこれのためにアセンブリを書いた。FFmpegの開発者たちが誇らしがってる。見てみい。実際は直接アセンブリを書いてるわけやないけど、メモリレジスターを直接叩く、めっちゃ低レベルなCをやってる。めっちゃ面白い。
SIMDのもんはクソ混沌や。それをやってくれたことに敬意を表する。彼らが同じコンテンツを連続で500回、4000層の深さまでシリアライズできるように、これらすべての最適化をしたことを想像してみい。混沌や。
これはええ説明やから、ここに載せるで。ありがとう、NMG。SIMDはSingle Instruction, Multiple Dataの略や。ARM64 NEONは ARMのSIMD命令の実装や。ワイの知る限り、それは正しい。
それは長い文字列のためのもんやけどな。ハードウェア命令の設定コストが高すぎる短い文字列については、SWORと呼ばれる技術を使ってる。これはSIMD Within A Registerや。このアプローチは、標準的な汎用レジスターでの巧妙なビット単位論理を使って、めっちゃ低いオーバーヘッドで一度に複数の文字を生成する。
うわあ。JavaScriptが世界で最も先進的な言語で、多くの面でV8のJSONは他の完全なプログラミング言語よりも先進的で技術的に徹底してるのは面白い。この人たちはRustを構築できるのに、代わりにワイらのJSON.stringifyを速くしてくれてる。
美しい。素晴らしい。ソフトウェアの歴史で行われた最も困難なエンジニアリング作業、最も先進的な低レベル最適化の一部が、ChromeのV8でJSONを速くするためになされた。
V8のコード行数
V8が何行のコードかワイは知ってるか?Canada Honkに聞かれたけど、知るのが怖い。これはすぐに答えられる豆知識やろ?気分悪くなりそうや。数字を落としてくれ。
160万。そやから、npm パッケージはV8のコード行数より2倍多い。せやけど、それらが多少近いっちゅうのはほんまに面白い。そしてJSCも80万や。くそったれ。V8のバンドルは9メガバイトや。コンパイルされたソースが9メガバイトになる。うわあ。
コメント、追加コード、テスト、その他すべてを含めると、総行数は300万近くになるらしい。知らん方がええ情報もある。それは痛い。せやけど少なくとも、くそったれなJavaScriptオブジェクトをもっと速くシリアライズできるようになった。
高速レーンの実装
方法に関係なく、プロセスは高効率や。文字列をチャンクごとにすばやくスキャンする。チャンクに特殊文字が含まれてへんかったら、これは一般的なケースやけど、文字列全体を単純にコピーできる。
そして高速パスにさらなる高速レーンがある。メインの高速パスでも、さらに速い高速レーンの機会を見つけた。デフォルトでは、高速パスはオブジェクトのプロパティを繰り返し処理せなあかんし、各キーに対して一連のチェックを実行せなあかん。
キーがシンボルやないことを確認する。列挙可能であることを保証する。そして最後に、エスケープを必要とする文字がないか文字列をスキャンする。
これを排除するために、オブジェクトの隠れクラスにフラグを導入してる。オブジェクトのすべてのプロパティをシリアライズしたら、プロパティキーがシンボルやなく、すべてのプロパティが列挙可能で、プロパティキーにエスケープを必要とする文字が含まれてへん場合、その隠れクラスを高速JSON反復可能としてマークする。
V8の隠れクラス
クールや。V8の隠れクラスは愛さなあかんな。これらはワイらがいるJavaScript層では必ずしもアクセスできへん実装の詳細がぎょうさんあるけど、V8が最適化をするためにそこにある。
V8は隠れクラスなしでもできる。確かに、各オブジェクトをプロパティの袋として扱うやろう。しかし、めっちゃ有用な原理が放置されることになる。インテリジェントデザインの原理や。
そやから、彼らは文字通り隠れクラスがないのは非知的やと言ってる。V8は、君がそんなにたくさんの異なる種類のオブジェクトを作ることはないし、各種類のオブジェクトは最終的にステレオタイプ的な方法で使われることになると推測する。
最終的に見られるって言うのは、JavaScript言語はスクリプト言語で、プリコンパイルされたもんやないからや。そやからV8は次に何が来るかわからへん。そやから、これが複数回使用するオブジェクト形状があるんやったら、V8が気づいて、このオブジェクトの形状を知って識別してマップして、より効率的に使用できる構造体を作るっちゅうことや。
オブジェクト形状の具体例
ここに関数があるけど、この関数ではthisを使ってる。絶対にやったらあかんけど、やることはできる。そして、extraDataが数値やなかったら、experienceに入れる。そうでなかったらprominenceに入れる。めっちゃ興味深い。そやから、nameとheightの両方を持つnew peakを呼び出すことで、これら2つを取得する。
せやけど、数値を入れたからこれにはprominenceがあって、文字列を入れたからこれにはexperienceがある。このコードで、root mapとも呼ばれる初期マップから、すでに興味深いマップツリーを取得してる。これは関数peakに付いてる。そやからnameでマップする。
それからheightでマップする。そして今、prominenceとexperienceで2つの異なるマッピングがある。各青いボックスは初期のもんから始まるマップや。これは、何らかの方法で一つのプロパティも追加せずに関数peakを実行したら返されるオブジェクトのマップや。
フォローアップのマップは、マップ間のエッジの名前で与えられたプロパティを追加することで生じるもんや。各マップは、そのマップのオブジェクトに関連付けられたプロパティのリストを持ってる。そして、なんでこれらすべてにストップがあるか疑問してるんやったら、関数が実行されるとき、まだどのプロパティも定義されてへんからや。関数が実行されるだけや。そやからthisに値が付いてない状態で始まって、それからnameがある。
そやから今、何もないバージョンとnameがあるバージョンがある。今、heightがある3番目のバージョンがある。そして今、4番目または5番目のバージョンがある。両方にはなれへん。この条件によってどちらか一方や。それらがマップや。マップKVストアみたいなもんやなくて、これらの形状に存在できる異なるキーのマッピングや。
これらのマップの一つから、例えばmap 3、これはpeakの追加引数に数値を渡した場合に取得するオブジェクトの隠れクラスやけど、初期マップまでバックリンクを辿ることができる。追加情報でもう一回試してみよう。興味深い。そやから、ここでの重要なポイントは、オブジェクトフィールドの場所がより低いレベルでマップされてるから、特定のキーを求められたときに、それがメモリのどこにあるかをより速く把握できるっちゅうことや。
そやからnameはキーI0を取得し、heightはキーI1を取得し、それからprominenceまたはexperienceはキーI2を取得する。マッピングを完了するためには7つのオブジェクトを作成せなあかんらしい。それが完了したら、peakオブジェクトは正確に3つのinobjectプロパティを持って、直接オブジェクトにさらに追加する可能性はない。
追加のプロパティはすべて、オブジェクトのプロパティバッキングストアにオフロードされる。それはプロパティ値の配列で、そのインデックスはマップから来る。まあ、技術的には記述配列や。そうや。そやから、m2.costみたいな追加の値を追加すると、costをこの一般的なキーの定数として持つ新しいマッピングが作成される。
繰り返すけど、これはオブジェクトに共通の形状があるとき、キーをメモリに保存されてる場所にずっと速くマップできるようにするためや。この場合のcostみたいに、形状に追加された一般的やない部分があるとき、それはルックアップがちょっと高価になるけど、実際の問題を引き起こすほど迷惑やない。
この説明をしようとするワイの試みがOliverをかなりイライラさせてるのは確実や。ベストを尽くしてる。事実の間違いがあったら教えてくれ。V8についてのおもしろい事実がぎょうさんある。数秒で脳を痛めることができる。想像もできへん。ワイはかなりええ。よし、うまくいってる。すぐに台無しにしよう。
アヒルの例で隠れクラスを説明
誰かがアヒルに言及したから、それができる。ワイらはみんなクラスとオブジェクト定義のための動物が好きやろ?アヒルには名前があって、身長があって、体重や足やその他何でもある。この新しいP0に追加されたフィールド、このcostフィールドは、着てるシャツみたいなもんやと考えることができる。アヒルの一部やないけど、ワイらがアヒルにあげたもんや。そやから今、アヒルと一緒にそこにある。せやけど、アヒルを分析しようとするとき、これは足の数を数えるよりも高価になった。
ワイらは今、JSと特にV8での隠れクラスをなんとなく理解してる。前にシリアライズしたオブジェクトと同じ隠れクラスを持つオブジェクトをシリアライズするとき、これはかなりよくあることやけど、すべて同じ形状を持つユーザー配列の束があるユーザー配列みたいなもんでは、めっちゃ意味がある。
同じタイプのユーザーがぎょうさんあるユーザー配列をシリアライズしようとしてるんやったら、最初の後に隠れクラスを見つけて、どうシリアライズするかを知ってる。そやから、このシリアライズの認識を使って、この配列の他のすべてに対して再計算する必要がない。
そやから、それを知ってて、高速JSON反復可能クラスやったら、これらのフィールドのそれぞれのメモリの場所を知ってるから、さらなるチェックなしで、すべてのキーを文字列バッファに単純にコピーできるようになった。それはかなりクールや。なんでかっていうと、繰り返すけど、これらのフィールドのそれぞれのメモリの場所を知ってる。
そやから、map 3か4でも知ってて、ガベージコレクションや副作用を心配する必要がないから、まったく気にする必要がないことも知ってるんやったら、それらのメモリの場所から新しい文字列に文字通り直接取り出すことができる。ほんまにクールや。
配列を解析するときに高速なキー比較に利用できるJSON.parseにもこの最適化を追加した。配列内のオブジェクトが同じ隠れクラスを持つことがよくあると仮定してや。
JSONパースの方が速い場合
それは巨大で、JavaScriptとV8でのワイの大好きな豆知識の一つに接線できる。これは論争の余地がない事実や。これは本当で、多くの場合、オブジェクトをインラインで定義するよりも、JSON.parseに文字列として渡すことでオブジェクトを定義する方が速いっちゅうことや。ワイらが常に作業してる言語とエンジンでの大好きなキーの癖の一つや。
JSONの文法はJavaScriptの文法よりもずっと単純やから、JSONはJSよりも効率的に解析できる。この知識は、大きなJSON風の設定オブジェクトリテラル(インラインReduxストアみたいな)を出荷するWebアプリのスタートアップパフォーマンスを改善するために適用できる。このようにインライン化する代わりに、JSONで入れることができる。
これがどんだけカオスか理解してるか?そしてReduxを例として出してきた?アプリケーションの開始点である複雑なオブジェクトがあって、いろんなキー、クレイジーな配列、その他いろんなもんがあるんやったら、JavaScriptは解析すべきもんがぎょうさんあるから、実際にJSONよりも遅くなる。なんでかっていうと、繰り返すけど、それは限定された文法やからや。
JSONには本当に3つのタイプしかない。文字列、数値、オブジェクト、そしてブール値もあると思う。そやから、JSONを解析するのは実際にめっちゃ効率的や。そして今、シリアライズとデシリアライズするもんの隠れクラスに基づいて、メモリの場所とレジスターを再マップして再使用する方法を見つけたから、さらに速くなった。
最適化の注意点
そして、めっちゃはっきりさせとくけど、これらがアプリを遅くしてるもんになるのは難しいやろう。せやけど、あらゆる角を最適化したいタイプやったら、Chromeのプロファイラーを通ることで、おそらくもうこれを学んでる。せやけど、値を割り当てることに実際の時間が費やされてるのを見てるかもしれへん。JSONでやってみて、何が起こるか見てみい。
新しい最適化に戻る – より速いdouble to string アルゴリズム
この新しい最適化に戻る。より速いdouble to stringアルゴリズム。数値を文字列表現に変換するのは、驚くほど複雑でパフォーマンスクリティカルなタスクや。JSON.stringifyでの作業の一環として、コアのdouble to stringアルゴリズムをアップグレードすることでプロセスを大幅に高速化する機会を特定した。
長年使われてきたGrisu 3アルゴリズムを、最短長数値から文字列への変換のためのDragon Boxに置き換えた。数値を文字列に変える方法がこんなに複雑で、いろんな実装があって、V8が何十年も存在してる後に、ついに新しいもんに移行したっちゅうのは、考えるとクレイジーや。
これはDragon Boxのリファレンス実装やけど、これは浮動小数点バイナリ十進数を文字列に変換する方法についての理論的なクソ研究や。数値を可能な限り効率的に文字列に変える方法について、完全に研究論文があるのが笑える。そして、これはC++でのその実装で、JSON.stringifyするときに数値がより速く文字列に変換されるように、V8に組み込まれた。
asm.jsの思い出
asm.jsについて知ってる。この日々を覚えてる。元のWebアセンブリや。ワイは君よりちょっと年上やと思う。そやから、これが起こったときにワイはそこにいた。全体の瞬間やった。asm.jsを復活させたくない。WASMが正しい解決策や。JSでアセンブリを動作させるべきやない。Webでアセンブリのように動作するもんを作るべきや。
君は20歳や。くそったれ。君がどんだけ若いか忘れてた。イエス。君の知恵は、たった20年の死すべき殻には賢すぎる。そうや、君は知りすぎてる。
最適化はJSON.stringifyのプロファイリングによって推進されたけど、新しいDragonBox実装はV8全体のnumber.prototype.toStringへのすべての呼び出しに恩恵をもたらす。
そやから、数値に対してtoStringを呼び出しすぎることでアプリが遅いんやったら、今はそれほど遅くないかもしれへん。クールや。これは、JSON シリアライゼーションだけやなく、数値を文字列に変換するあらゆるコードがこのパフォーマンスブーストを見ることも意味する。素晴らしい。
さらなる最適化 – 基礎となる一時バッファの最適化
そしてさらなる最適化。基礎となる一時バッファの最適化。文字列構築操作での大きなオーバーヘッドの源は、メモリがどう管理されるかや。以前、ワイらの文字列化器はC++ヒープの単一の連続バッファで出力を構築してた。
単純やったけど、このアプローチには大きな欠点があった。バッファが容量不足になるたびに、より大きなもんを割り当てて、既存のコンテンツ全体をコピーせなあかん。大きなJSONオブジェクトの場合、再割り当てとコピーのサイクルが主要なパフォーマンスオーバーヘッドを作り出した。
興味深い。繰り返すけど、バッファオーバーフローは、エンジンが常にクソを吐き出して、機会があるときにガベージコレクトせなあかんときの大きな懸念や。重要な洞察は、この一時バッファを連続にすることを強制するのは、最終結果が最後にのみ単一の文字列に組み立てられるので、本当の利益を提供しへんっちゅうことやった。
これを念頭に置いて、古いシステムをセグメント化されたバッファに置き換えた。すべてを単一の大きな成長するメモリブロックに入れる代わりに、V8ゾーンメモリで割り当てられた小さなバッファまたはセグメントのリストを使うようになった。セグメントがいっぱいになったら、新しいもんを割り当てて、そこで書き続ける。これで高価なコピー操作が完全に排除される。
それはほんまにクール。これらの実現がソフトウェアでの重要なイノベーションをどれだけ推進してきたかに驚くやろう。「ああ、ワイらはこれを全部一箇所に保存してる。最後まで全部使わへん。最後にとにかく一緒に追加するんやったら、どこでも保存して最後に一緒に追加したらどうや?」
セキュリティ上の懸念
Canada Honkからのほんまにええポイント。ここでの恐ろしいことは、そのコードのどこかに1行の間違いがあったら、理論的には、サンドボックスエスケープで、JSONを解析することでRCEを引き起こす可能性があるっちゅうことや。恐ろしい。実際に恐ろしい。これがバイブコーディングされなかったことにめっちゃ感謝してる。
GPT-5での実験
面白いことを試してみよう。意図的にGPT-5にインターネットアクセスを与えへん。検索も与えへん。V8エンジン内でJSON.stringifyのパフォーマンスを改善できるいくつかの理論的な方法は何かと聞いてみる。V8のソースコードに望む変更を加える能力があると仮定して。何が出てくるか見てみよう。GPT-5が何て言うか気になる。
これらの最適化をバイブコーディングするとどうなるか知りたい。そしてそれを待ってる間に、制限について話そう。
制限事項
新しい高速パスは、一般的でシンプルなケースに特化することで速度を達成してる。シリアライズされるデータがこれらの基準を満たさへん場合、V8は正確性を保証するために汎用シリアライザーにフォールバックする。完全なパフォーマンス恩恵を得るために、JSON.stringify呼び出しは以下の条件に従わなあかん。
replacerやspace引数なし。replacer関数やきれいな印刷のためのspaceまたはgap引数を提供するのは、汎用パスでのみ処理される機能や。面白い。
そやから、JSON.stringifyが出力をよく見せるためのすべての派手なことをやる機会があることをしてるんやったら、速度から外れる。意味がある。
プレーンなデータオブジェクトと配列。シリアライズされるオブジェクトはシンプルなデータコンテナーでなければならへん。これは、それらとそのプロトタイプのどれもがカスタムtoJSONメソッドを持ったらあかんということを意味する。
もし自分のtoJSONを定義してるんやったら、ワイらよりも深いところにいる。ええ。クールや。楽しんで。せやけど、カスタムtoJSONがあったら、これらのメモリ割り当てレベルの変更みたいなことはできへん。レジスターを読んで何をすべきかを知ることができへん。意味がある。必要やないんやったらカスタムなクソを書かへん理由がもう一つ。
高速パスは、カスタムシリアライゼーションロジックを持たへんobject.prototypeやarray.prototypeみたいな標準プロトタイプを想定してる。そして、これがtoJSONをオーバーライドできることを学んだ方法やったら、やったらあかん。自分をこれに通さへんでくれ。価値がない。ワイはその関数でひどいことをしたことがある。やったらあかん。信じてくれ。
そして、二つのもんを足したときに何が起こるかをオーバーライドできることを学ぶことがないことを願ってる。危険が待ってる。旅人よ、めっちゃ気をつけてくれ。
オブジェクトでの制限
オブジェクトにインデックスプロパティなし。高速パスは、通常の文字列ベースのキーを持つオブジェクトに最適化されてる。オブジェクトが0、1などのような配列風のインデックスプロパティを含んでる場合、より遅い一般的なシリアライザーによって処理される。
オブジェクトを配列のように使わへん理由がもう一つ。オブジェクトをオブジェクトのままにしておけ。
シンプルな文字列タイプ。const stringみたいないくつかの内部V8文字列表現は、シリアライズされる前にフラット化するためにメモリ割り当てを必要とする可能性がある。高速パスは、そのような割り当てを引き起こす可能性のあるあらゆる操作を避けて、シンプルな順次文字列で最もうまく機能する。
これはWebデベロッパーとして影響するのが難しいもんやけど、心配すな。ほとんどの場合、うまく機能するはずや。
API応答のデータをシリアライズしたり、設定オブジェクトをキャッシュしたりするような大多数の使用例では、これらの条件は自然に満たされて、開発者が自動的にパフォーマンスの改善から恩恵を受けることができる。
GPT-5の予測結果
T3チャットのGPT-5が、ワイらが理論的にこれを最適化できると考える方法はこれや。これがそのもんのいくつかを推測してるか見てみよう。
見てみい。実際にいくつかのことを推測してる。オブジェクトがデータプロパティのみで構成されて、安定したマップと隠れクラスを持って、辞書モードやない場合、穴を意味する要素がない場合。仕様が許可してるか最大スループットのために非ASCII エスケープなしで保持。そしてガードが失敗したら、既存の一般的なパスにデオプト。
同じマップを持つオブジェクトのための形状ベースのプロパティプラン。列挙順序でプロパティフィールドの安定したリストをプリコンパイルして、一般的なプロパティキー収集をスキップして、フィールドを直接読むことでシリアライズ。
これらが計画をトレーニングしたか?これは的確や。要素がマップを共有するとき、配列をシリアライズするんやったら、要素全体でプロパティプレーンを巻き上げて再利用。これは、ワイらが話した配列の反復でもある。
これをかなりうまく推測してる。おお、見てみい。SIMD ベクター化されたASCIIスキャンまで含んでる。それは笑える。1バイト文字列用の1つと2バイト文字列用の1つの2層文字列パス。ほとんどを取得した。
V8での文字列シリアライゼーションでのパフォーマンス向上をバイブコーディングできたことがわかる。誰が思ったやろう?ワイは思わへんかった。ワイらがこれをバイブコーディングできるとは絶対に思わへんかった。
はっきりさせとくけど、ワイらはこれをバイブコーディングできへん。せやけど、答えのほとんどをこのように得られるのはかなりクールや。それはめっちゃクールや。
結果
そして今、結果を見てみよう。美しい。なんでPixel 9がM1 Macよりもストリングをシリアライズするのが速いんや?それは痛い。でも実際は結論を読むべきやな。
高レベルのロジックからコアメモリと文字処理操作まで、JSON.stringifyをゼロから再考することで、Jetstream 2 JSON.stringify inspector benchで測定されて、2倍以上のパフォーマンス改善を実現した。
以下の図を見てくれ。これらの最適化は、Chrome 138であるV8バージョン13.8から利用可能や。
ああ、時間やなくてスコアや。訂正してくれてありがとう、Nean。ベンチマークは時間ベンチマークやなくて、スコアベンチやから。そやから、実際はバーが高い方がええ。Macユーザーが勝ってる。
それを受け取る。これは素晴らしかった。これは、この情報を消化可能にする実際にほんまにええ書き上げやった。そして、これは消化可能であるべき情報やない。これを書いたPatrickに大きな賞賛を送るし、ワイがこの件で情報を間違えへんように確認してくれたOliverにも大きな賞賛を送る。
まとめ
彼らは史上最も賢いJSエンジンの人の一人や。ピリオド。ワイがJavaScriptについて話すことの多くは、Oliverから直接得た情報や。素晴らしいリソース。死ぬほど愛してる。もしJavaScriptエンジンでの彼らの完全な狂気を追跡したいんやったら、絶対にフォローすべき人や。
ああ、JSON.stringifyみたいに、ここからすぐに出る時間やと思う。Berts。


コメント