Published on

フロントエンドパフォーマンスチューニング

Authors
  • avatar
    Name
    Kikusan
    Twitter

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ならば改善方針が異なる
  • クリティカルレンダリングパスを短く:レンダリング処理に至るまでの時間
    1. HTMLのダウンロード、DOM構築:DOMContentLoaded
    2. ①CSSのダウンロード、スタイル評価 ②JavaScriptのダウンロード、実行:(load)
    3. レンダリング(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 されたファイルを配信する方法
    • 画像の最適化
      1. SVG: 拡大縮小に強くCSSでスタイルを適用できる。DOM APIも使用可能。複数のファイルサイズを用意する必要がない。アクセシビリティ対応もSVG画像内で完結する。
        gzip配信することでラスタ型よりもファイルサイズは小さくなる。
      2. WebP: 可逆圧縮も非可逆圧縮も対応し、アニメーションや透過にも対応している。ファイルサイズも既存の形式より小さいが、ブラウザの対応状況が悪い
      3. 透過が必要もしくは高解像度が必要な場合は、PNGを使用する
      4. 再保存されない、または劣化が許容できる場合、JPEGを使用するとファイルサイズが圧縮できる
      5. アニメーションが必要な場合は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の構築を早める

レンダリング処理

低スペックデバイスでもスムーズに動くパーツであるか

アプローチ方法

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: 他のオブジェクトへの参照によって確保されるサイズ
    • Allocation instrumentation on timeline : 計測期間内のメモリ状態変化を取得できる
      • 見方は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