HeadlinesBriefing favicon HeadlinesBriefing.com

ブラウザ メインスレッド パフォーマンス

Hacker News •
×

「フロントエンド最適化」と聞いて何を思い浮かべますか?私たちの多くにとってそれは、ネットワークリクエストの削減、バンドルサイズの縮小、キャッシュの有効活用といったことです。それ以外では、再レンダリングの削減やリソース読み込みタイミングの調整などがあるかもしれません。メインスレッドが話題に上ることはめったにありません。それには理由があります:ほとんどの画面では決して問題にならないからです。しかし、インタラクションが多く、データがリアルタイムでストリーミングされ、スクロール、アニメーション、入力がすべて絡み合っている画面では、状況が変わります。ネットワークとバンドルサイズでどれだけ節約しても、メインスレッドがブロックされる瞬間、画面はフリーズします。スクロールが時々カクつく、ボタンの反応がわずかに遅れる、検索ボックスに入力した文字が半拍遅れて表示される、そんなウェブサイトに遭遇したことがあるはずです。イライラするほどではないけれど、微妙に神経を逆撫でされる。その種のジャンク(カクつき)こそが、ブロックされたメインスレッドの正体です。開発者としてこの種のジャンクに遭遇したとき、通常の反応は「自分のコードが遅いのでは?」と疑い、アルゴリズムを分解したり無駄な計算を探したりすることです。しかしほとんどの場合、コードの速度は問題ではありません。コードは遅くないのです。たまたまメインスレッドを占有しているコードなだけです。ブラウザには複数のスレッドがありますが、コードから触れるほぼすべてのものはメインスレッドに集中しています。計算、レンダリング、イベント処理、ネットワークレスポンス処理、フレームワークの内部処理など、すべてがここで処理されます。一つのリソース、山のような仕事。ブラウザのメインスレッドは高コストです。大抵の場合は問題になりませんが、野心的なことをしようとすると、メインスレッドへの対処が重要になります。この記事は、その高コストなリソースをどう扱うかについてです。まずメインスレッドが実際に何をしているかから始めましょう。その仕事は大まかに二つのカテゴリに分けられます。第一は JavaScript の実行です。私たちが書いたコード、イベントハンドラ、タイマー、ネットワークレスポンスのコールバック、フレームワークの内部処理など、すべてがここで実行されます。これらのタスクはキューに入った順序で、隙間時間があれば実行され、画面のリフレッシュサイクルとは無関係です。第二は画面の描画です。DOM やスタイルが変化して画面の更新が必要になると、ブラウザはおおよそ以下の手順を順番に経てフレームを生成します。requestAnimationFrame コールバックの実行 — フレーム描画の直前に実行するよう登録された JavaScript スタイル計算 — 各要素の最終的な CSS 値を計算 レイアウト — 各要素の位置とサイズを計算(リフローとも呼ばれる) ペイント — 何をどの色でどこに描画するかを記述するペイントコマンドを生成 何も変わっていなければこれらのステップは完全にスキップされるため、必ずしも毎フレーム実行されるわけではありません。最後の合成ステップのみ、生成された出力を取り込み画面上に組み立てる処理が、コンポジタースレッドに委譲されます。つまり、画面を描画するパイプラインの前半部分の大半はメインスレッドの責任です。画面を更新するためのレンダリングパイプライン 画面を滑らかに見せるには、フレームをディスプレイのリフレッシュレートで描画しなければなりません。最も一般的な 60Hz ディスプレイでは、毎秒 60 フレーム、つまりフレームあたり約 16.6 ミリ秒です。そしてそのすべてを使えるわけではありません。ブラウザ自体の処理コストを差し引くと、実用的な予算は通常 10 ミリ秒程度と見なされ、120Hz デバイスでは予算自体が半分になります。問題は、上記の二種類の仕事が同じスレッド上で一列に並んでいることです。JavaScript はシングルスレッドのイベントループモデルを前提に設計されました。メインスレッドは一度に一つのタスクを処理し、そのタスクが実行されている間は他の何も起こせません。もし一つの JavaScript 関数が 200 ミリ秒実行されれば、その 200 ミリ秒間、ブラウザは画面を再描画できず、ユーザーのクリックを受け取ることもできません。約 10 ミリ秒のフレーム予算に対して、これは致命的な時間です。実行時間が長すぎるタスクが...