- Published on
フロントエンドパフォーマンスチューニング
- Authors
- Name
- Kikusan
- refs
- Tools
- パフォーマンス指標
- 3つのポイント
- Response Times: The 3 Important Limits
- RAILモデル
- User centric performance metrics
- ネットワーク処理
- アプローチ方法
- 計測方法
- 対策
- レンダリング処理
- アプローチ方法
- 計測方法
- 対策
- スクリプト処理
- アプローチ方法
- 計測方法
- 対策
- Appendix: WebWorkerAPI と ServiceWorkAPI
refs
Tools
基本的なチェックリストツールを使用する
- DevToolsのパネル:Lighthouse
- PageSpeed Insights (google提供)
パフォーマンス指標
3つのポイント
- ネットワーク処理:コンテンツダウンロードに時間がかかっている
- DNSルックアップ
- SSL/TCP3ウェイハンドシェイク
- HTTPレスポンス
- レンダリング処理:画面の更新が遅い
- スクリプト処理:APIが遅い、スクリプト内の処理が遅い
Response Times: The 3 Important Limits
応答時間の限界値
RAILモデル
User centric performance metrics
ネットワーク処理
アプローチ方法
- 三原則
- 転送量を小さく:圧縮・最適化
- 転送回数を少なく:リクエスト回数
- 転送距離を短く:キャッシュ、ホストロケーション
- HTTP/2ならば改善方針が異なる
- クリティカルレンダリングパスを短く:レンダリング処理に至るまでの時間
- HTMLのダウンロード、DOM構築:DOMContentLoaded
- ①CSSのダウンロード、スタイル評価 ②JavaScriptのダウンロード、実行:(load)
- レンダリング(DOMツリーとCSSOMツリーからレンダーツリーを構築、ピクセルを配置)
計測方法
- 合成モニタリング:計測用環境から定期的にモニタリングを行う
- WebPagetest:google提供の合成モニタリングサービス。通信処理のチャートや一定時間毎にスクリーンキャプチャがとれたり。プライベートインスタンスも作れる。
- リアルユーザーモニタリング:実際のエンドユーザが操作する都度、専用スクリプトで実測データをサーバへ投げる
- Timing API:ブラウザのページロードにおける各段階にかかった時間をJavaScriptで取得できる
https://developer.mozilla.org/ja/docs/Web/API/Performance_API
performance.mark('fetchStart') // 開始マーク
performance.mark('fetchEnd') // 終了マーク
performance.measure('fetch', 'fetchStart', 'fetchEnd') //計測
- Networkパネル:各リソースの取得時間が確認できる。 SizeやLatency(ダウンロード開始までの時間)でソートできる 各リソースの詳細のTimingから、ダウンロードにそれぞれどれだけかかったかわかる。
- Stalled~Request sent:疎通まで
- Waiting(TTFB) Time To First Byte
- Content Download
Tips: NetWorkタブのリソース上でShift+ホバーをすると、取得するきっかけのリソース(initiators)と、取得する元となったリソース(dependencies)が見える
対策
- サイズの大きなファイルを圧縮する
- Minification : ビルドツールで最小化する : https://ja.vitejs.dev/config/build-options#build-minify
- Tree Shaking : 不要コードの削除 : viteは自動で行う
- Code Splitting : サイズの大きなJavaScriptの分割 : https://vitejs.dev/guide/build#chunking-strategy
- テキストはgzipで配信する : Vite + CloudFront で gzip されたファイルを配信する方法
- 画像の最適化
<picture>
やimgタグのsrcset
属性、メディアクエリを使用して読み込む画像を制御する- CIツールやプログラムによって最適化を行う
<img>
要素のHeightとWidthを指定することで、画像読み込み中のレイアウト算出を抑制できる。
- SVG: 拡大縮小に強くCSSでスタイルを適用できる。DOM APIも使用可能。複数のファイルサイズを用意する必要がない。アクセシビリティ対応もSVG画像内で完結する。
gzip配信することでラスタ型よりもファイルサイズは小さくなる。 - WebP: 可逆圧縮も非可逆圧縮も対応し、アニメーションや透過にも対応している。ファイルサイズも既存の形式より小さいが、ブラウザの対応状況が悪い
- 透過が必要もしくは高解像度が必要な場合は、PNGを使用する
- 再保存されない、または劣化が許容できる場合、JPEGを使用するとファイルサイズが圧縮できる
- アニメーションが必要な場合はGIF
- 画像のデータの持ち方
- ラスタ型:ピクセル1つ1つに対応する色情報を持つ。ベースライン型とプログレッシブ(インタレース)型のどちらかで保持する
- ベースライン型のファイル(画像上部からレンダリングする)
- プログレッシブ型(画像全体を低解像度から徐々にレンダリング): UXが良い
- ベクタ型:点の座標と線をデータ化してラスタライズする形式。SVG
- 画像の圧縮方式(ラスタ型)
- 可逆圧縮:アルゴリズムによって短く表現、デコードで元に戻せる。GIFやPNG、WebP
- 非可逆圧縮:劣化を感じにくい部分を省略する。JPEG、WebP
- TTFBを減らす
- バックエンドチューニング
- API分割
- TTFBが減らせないなら、アプリ側でprefetch
- 実行結果をWebStorageやIndexedDBに保存する
- ブラウザキャッシュをさせる : https://tech.briswell.com/entry/2023/07/13/193515
- ETag : レスポンスヘッダの検証トークンで、差分がなければ304(Not Modified)を返す。
- Cache-Control : リソースの変更を見にいくか、時間切れで見にいくか、毎度取得するか、もう取得しないかを指定
- CDNを使用する
- リクエスト数を減らす
- アプリコードをバンドルする(依存関係は更新が少ないため分割しておくとよい) : viteは自動で行う
- lazy load を使用して見えない位置の画像をロードしないようにする
- (HTTP/2では並行リクエストがあるため、オーバーヘッドが小さくなり結合の意義が小さい。)
- DOM、CSSOMの構築を早める
- scriptにasyncやdeferをつける : https://qiita.com/phanect/items/82c85ea4b8f9c373d684
- フォントファイルの最適化、Font Loading APIやfont-displayディスクリプタを使用する
- Cacheを長期間で行う
- サブセット化を行う : https://zenn.dev/kymok/articles/c85952bd74a2cf
レンダリング処理
低スペックデバイスでもスムーズに動くパーツであるか
アプローチ方法
FPSを基準に考える : 60Hzのモニターであれば1フレームは16.7ミリ秒。この間にレンダリング処理が終わるようにする
- 1フレーム内の処理を軽減すること
- ブラウザの内部処理による最適化を生かすこと
計測方法
Performanceパネルを使用する。 : https://zenn.dev/koki_tech/articles/9deb70d0a9befb
Loading(青): HTTPリクエストやHTMLのパースなど
Scripting(黄): JavaScriptで行われた処理全般
Rendering(紫): スタイル処理やレイアウト算出など
Painting(緑): ペイント処理やラスタライズなど
CPUスロットリング: Performanceパネルの CPU: の選択肢から、1/6などにできる
対策
- 効率の良いレンダリングを選択する
- DOMアニメーション : DOM要素をJavaScriptで更新
- CSSTransitions/Animations
- Web Animations : CSSとSVGアニメーションを統合したJavaScriptAPI
- Compositingの有効化 : GPUを有効に使用する : https://qiita.com/yuki153/items/9aac0e5c8d7230a7bbe2
スクリプト処理
JavaScriptの実行に関わるロジック、レンダリング処理の問題
JavaScript処理が長いとメインスレッドを占有してしまう
アプローチ方法
- UIブロッキングに繋がる長大な処理を避ける
- メモリリークを回避し、メモリを節約する
計測方法
PerformanceパネルとMemoryパネルを使用する
- Performanceパネル : https://web.dev/articles/optimize-long-tasks?utm_source=devtools&hl=ja
- Mainセクションでフレームチャートをみられる。呼び出し関数が多いほど下にスタックする。
- Taskの詳細プロファイルのBottom-Upを見ることで重い処理をソートしてみられる。
- Memoryパネル
- Heap snapshot : クリックした時点のヒープ領域のスナップショットが記録される。
- Summaryビューによってコンストラクタ名ごとにメモリの総容量をみられる
- Shallow Size: オブジェクトそのもののサイズ
- Retained Size: 他のオブジェクトへの参照によって確保されるサイズ
- Summaryビューによってコンストラクタ名ごとにメモリの総容量をみられる
- Allocation instrumentation on timeline : 計測期間内のメモリ状態変化を取得できる
- 見方はHeap snapshotと同様。時間軸のバーの大きさが大きいところが、メモリを食っているところ。
- Heap snapshot : クリックした時点のヒープ領域のスナップショットが記録される。
対策
- 処理の非同期化
- 通信を非同期で走らせる
- 高頻度処理を
throttle()
やdebounce()
で間引く - 即時性が必要ない処理を
requestIdleCallback()
でブラウザがアイドルしている時に走らせる - ブラウザAPIに関与しない計算処理を、WebWorkerAPIで別スレッドに移す
- 必要ないイベントリスナ、タイマー、オブジェクトを解放する
Appendix: WebWorkerAPI と ServiceWorkAPI
- どちらも別スレッドで処理を実行できる。
- Web Workerはブラウザからjsを起点に実行するのに対し、Service Workerはサイト経由でインストールされたのちイベントハンドラの処理が完了するまで、ブラウザが起動していなくても、オフラインでも動作する。
- 単純に重い処理を別スレッドで行う場合はWeb Worker、リクエストへの割り込み、サーバプッシュの受信、バックグラウンド同期などをしたい場合はService Workerを使用するとよい。
https://developer.mozilla.org/ja/docs/Web/API/Web_Workers_API https://developer.mozilla.org/ja/docs/Web/API/Service_Worker_API