Webアプリを速くする (計測編)

業務で開発しているReactアプリのパフォーマンス改善を求められたので、パフォーマンスを改善するまでにやったことをまとめました。
いくつかのパートに分けて記事にする予定です。
この記事ではReactなどのフレームワークに依存しない内容になります。

# パフォーマンス改善の進め方

とにかく計測して比較することが大事です。
「なんとなく速くなりそう…！」という憶測で先に実装してしまうのはよくないです。

1. **パフォーマンスを計測する**
2. 遅い箇所をみつける
3. (できれば)原因を特定、推測する
4. 改善する
5. **パフォーマンスを計測して比較する**

本記事ではパフォーマンスの計測手法と、遅くなっている箇所を特定するところまで解説します。

# 計測する環境

改修前後でできるだけ同じ環境を作り出すことが重要です。

- ブラウザ
    - 拡張機能を無効にする
        - Chromeであればゲストモードがおすすめ
    - キャッシュを削除、もしくは無効化する
- Webサーバー
    - developmentビルドだと正確に測れないためproductionビルドにする
    - localでサーバーを立ち上げる
- 同じユーザーを使う

# パフォーマンスの計測方法

Chrome DevToolsを使った方法を取り上げます。
FirefoxやEdgeの開発者ツールにも似たような機能があります。

DevToolsはChrome 94で日本語に対応しましたが、本記事では英語版で説明します。
(Chrome 107時点の記事のため、最新版と異なる場合があります)

## Lighthouse

パフォーマンスやアクセシビリティの品質を数値化するツールです。
計測すると改善すべき項目を教えてくれるので簡単に原因を特定することができます。
初めはこれを使ってパフォーマンスに問題がある箇所を特定するのがおすすめです。

![スクリーンショット 2022-12-04 21.25.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271825/21e9ed69-04da-a5fc-f83a-0278c854eb1a.png)

- [First Contenful Paint](https://web.dev/i18n/ja/fcp/) (FCP)
    - 初期ロードからコンテンツが最初にレンダリングされるまでの時間
- [Speed Index](https://developer.chrome.com/docs/lighthouse/performance/speed-index/)
    - どれぐらい早い段階でコンテンツが描画されているか
- [Largest Contentful Paint](https://web.dev/lcp/) (LCP)
    - 初期ロードから最も大きい要素がレンダリングされるまでの時間
- [Time to Interactive](https://web.dev/i18n/ja/tti/) (TTI)
    - 初期ロードから操作可能になるまでの時間
- [Total Blocking Time](https://web.dev/i18n/ja/tbt/) (TBT)
    - メインスレッドがブロックされているFCP〜TTI間の時間の合計
- [Cumulative Layout Shift](https://web.dev/i18n/ja/cls/) (CLS)
    - ページの表示中に発生した予期しないレイアウトシフトごとに算出されるスコア
    - レイアウトシフト: 表示された要素の位置が後から変わること

![スクリーンショット 2022-12-04 21.25.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271825/994e010e-c7bc-976c-c7e9-207ea221d98b.png)

OPPORTUNITIESにパフォーマンス改善のアドバイスが書いてあります。

## Performance

ブラウザで実行するあらゆる処理を可視化することができます。

![スクリーンショット 2022-12-04 21.43.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271825/5befd064-20fa-1a1a-a2cc-2d9286a9c16d.png)

### 測定手法

1回の計測だけだとブレがあるので最低3回は実行した方がよいと思います。

- Recordボタン: 手動で停止する
- Reloadボタン: ページリロード後自動で停止する
- Clearボタン: 測定結果を削除する

### 設定

- CPU / Network
    - パフォーマンスを落として計測すると原因が特定しやすくなる
    - この設定は測定していないときも作用する
- Disable JavaScript Samples
    - JavaScriptのコールスタックが必要ない場合はチェックする

### 測定結果の見方

横軸が時間軸で縦がカテゴリー別に分かれています。
スクロールで横軸の表示範囲を拡大・縮小することができます。

![スクリーンショット 2022-12-04 23.52.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271825/1356a98b-5326-f404-ad8e-5152c7c90120.png)

- Network
    - APIリクエストなどネットワークの状態
- Timing
    - Web Vitals(後述)などのイベントのタイミング
- Main
    - メインスレッドで実行している処理
    - その中のイベントをクリックすると、下のSummaryタブで詳細を確認できる
- Memory
    - メモリ使用量
    - 上部のチェックボックス「Memory」をオンにすると表示される
- Summary
    - イベントの内訳と実行時間

## Performance insights

最近登場した「Performance」に代わる計測手法です。
よりUIがすっきりして初心者にも分かりやすくなった印象です。
まだ使い出して間もないため説明は省きます。

https://developer.chrome.com/ja/blog/new-in-devtools-102/

## Network

APIリクエストや画像、JavaScriptなどのリソースの読み込みを監視できます。

![スクリーンショット 2022-12-05 1.27.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271825/434fa8b0-c607-4f18-8b17-2b8ec3c7e5c2.png)

### 設定

- Preserve log: ページ遷移前のログを保持する
- Disable cache: リソースのキャッシュを無効化する
    - パフォーマンスを計測する際は無効化しておく
- Throttling: ネットワーク速度をエミュレートする
    - 接続環境が悪い、もしくはオフラインの状況を再現できる

### 確認する項目

- Sizeをみて大容量のトラフィックが起きていないか
- Timeをみて時間がかかっているリクエストがないか
    - 1回の計測ではブレがあるので何回か同じリクエストを送って平均を計るとよい
    - 右クリックから `Replay XHR request` を選択すると同じpayloadで再リクエストできる

## Rendering

右上のハンバーガーメニューからMore tools → Renderingを選択すると、タブが表示されます。

- Frame Rendering Stats: FPSを表示する
    - ユーザー操作がカクカクしている場合は、FPSが低下していることを疑う

# 問題を発見する

計測結果を元に問題のある箇所を見つけます。

## ネットワーク

- 無駄にAPI Requestが発生していないか
- 時間がかかっているリクエストがないか
- 同期的にリクエストを送っていないか
- 巨大なJSファイルを読み込んでいないか
    - SPAでは特に注意
- 使っていない外部リソースを読み込んでいないか
- サイズが大きい画像を読み込んでいないか

## レンダリング

- 頻繁に再レンダリングが発生していないか
- Node数を減らせないか
- Elementのネストが深くないか

## 非同期処理

- 時間のかかっているタスクはないか
- 直列実行→並列実行にできるところはないか

# パフォーマンスの指標

代表的なパフォーマンスの指標(UXの指標)の一つにGoogleが提唱するWeb Vitalsがあります。
その中でも重要な観点がCore Web Vitalsと呼ばれています。

- Largest Contentful Paint (LCP)
    - 初期ロードから最も大きい要素がレンダリングされるまでの時間
- First Input Delay (FID)
    - ユーザーが最初にページを操作してから実際にブラウザがイベントハンドラーの処理を開始するまで
- Cumulative Layout Shift (CLS)
    - ページの表示中に発生した予期しないレイアウトシフトごとに算出されるスコア
    - レイアウトシフト: 表示された要素の位置が後から変わること

Core Web Vitalsの指標は逐次アップデートされていて、ユーザー体験がよいサイトとはなにかという定義は変わり続けています。

https://web.dev/learn-core-web-vitals/

# パフォーマンス比較が困難な場合

ツールを使っても比較が難しく、改善の効果の度合を判断できない場合があります。

- 論理的に高速になったことを証明する
- 画面キャプチャで比較する

などしてチームメンバー内で認識が合えばよいと思います。

# 参考資料

https://qiita.com/sykmhmh/items/56f1c34a2ff4bcfc6f3a

https://qiita.com/shin_moto/items/93e2141b14b022bb6bd0

https://zenn.dev/lollipop_onl/articles/eoz-devtools-rendering-panel

https://zenn.dev/koki_tech/articles/9deb70d0a9befb

https://engineering.mercari.com/blog/entry/2018-12-12-090156/