Chrome Developer Toolsでパフォーマンス計測・改善

Chrome Developer Toolsを使ったWebページのパフォーマンス計測・改善についての説明です
Networkパネル、Timelineパネル、Profilesパネルの使い方を説明してから
パフォーマンスの計測・改善について説明していきます

# Networkパネル
Networkパネルはページのリクエストをしてからの通信内容の一覧を表示します
![Network2.png](https://qiita-image-store.s3.amazonaws.com/0/54850/abc59343-0af8-17a9-ef9e-8c67a82500ff.png)

## 記録方法
左上のRecordボタンを押すと記録が始まる
もう一度押すと記録が停止する

必要に応じて、Disable cacheやCapture screenshotsを設定する

## 表示項目の変更
赤枠で囲んだ部分を右クリックすると
![Network_項目.png](https://qiita-image-store.s3.amazonaws.com/0/54850/4e8e7d4e-1490-883b-47da-4c2da6231411.png)
こんな感じでメニューが出てくるので表示したい項目をクリックする

項目の一例
　Name：リソースの名前
　Method：HTTPメソッドの種類
　Status：レスポンスのステータスコードとテキスト
　Type：リソースの種別
　Initiator：そのリソースがどこから読み込まれたか
　Size：リソースのサイズ
　Time：リソースのダウンロードにかかった時間
　Timeline：リクエストのダウンロード処理の詳細とかかった時間

Timelineをクリックするとソートの種類を選べます
![Network_Timeline選択.png](https://qiita-image-store.s3.amazonaws.com/0/54850/367e6c33-dc01-3b65-768c-5edccf87c47d.png)

・Start Time：リクエストの開始時間順
・Response Time：レスポンスの開始時間順
・End Time：完了した時間順
・Total Duration：トータル時間順
・Latency：リクエストを送信してから最初のレスポンスを受信するまでの時間(Waiting)順

## フィルタリング
![Network_フィルタリング.png](https://qiita-image-store.s3.amazonaws.com/0/54850/4614d05b-23a7-364b-2c6f-22e3660470aa.png)
赤枠で囲んだ部分にフィルタリングのキーワードを入力すると、条件に合うものだけ表示される

データの種類別にフィルタをかけることもできる（All、XHRとかの部分）
「Hide data URLs」にチェックを入れるとdataURIのリクエストを非表示にできる
（blobについては非表示にできないようです）

## リクエストの詳細
リクエストを選択しTimingタブをクリックすると詳細が表示される(Timelineにマウスを合わせてもOK)
![Network_Timing.png](https://qiita-image-store.s3.amazonaws.com/0/54850/28eef187-d183-471e-4a1a-5b2c26e456c4.png)

Timingタブは２つのフェーズに分かれている
**Connection Setup**（サーバとの接続をセットアップするフェーズ）
・Queueing：ネットワーク処理のキューイングに要した時間
・Stalled：プロキシのネゴシエーションを含んだ、TCPの接続制限による接続待ちなどによって発生する
　　　　　リクエスト開始までの時間
・Proxy Negotiation：プロキシサーバとの接続確立に要した時間
・DNS Lookup：DNSルックアップに要した時間
・Initial Connection：SSLやTCPのネゴシエーションを含めた初期接続確立までの時間
・SSL：SSLのハンドシェイクに要した時間

**Request/Response**（実際にデータのやりとりを行うフェーズ）
・Request sent：リクエストの送信に要した時間
・Waiting：リクエストを送信してから最初のレスポンスを受信するまでの時間
・Content Download：サーバからのレスポンスデータを受信するのにかかった時間

Timingタブ以外にもRequest/Response Headerの確認ができたり、Cookieの内容も確認できる
・Headersタブ：ホスト名とIPアドレス、クエリパラメータといったリクエストの基本情報および、
　　　　　　　　リクエストヘッダとレスポンスヘッダが表示される
・Previewタブ：返却されたテキストやバイナリデータがプレビューされる
・Responseタブ：返却されたデータがパースされていない生の状態で表示される
・Cookiesタブ：リクエストとそのレスポンスに付与されたCookieが表示される

## HAR(HTTP Archive)

Networkパネルのウィンドウ上で右クリックすると、↓このようなメニューが出てきます
![SaveHAR.png](https://qiita-image-store.s3.amazonaws.com/0/54850/4d01c7bf-cb0e-8bb5-f858-5fb74c13f28b.png)

「Save as HAR with content」でHARファイルを保存

[HTTP Archive Viewer](https://chrome.google.com/webstore/detail/http-archive-viewer/ebbdbdmhegaoooipfnjikefdpeoaidml?hl=ja)で同じように解析できます
![HTTPArchiveViewer.png](https://qiita-image-store.s3.amazonaws.com/0/54850/e2e1da67-b12e-35e6-3dd3-52811b2ae0f4.png)

# Timelineパネル
Timelineパネルではブラウザで起こった各種処理を時系列に記録しボトルネックがないか調査できます
![Timelineパネル.png](https://qiita-image-store.s3.amazonaws.com/0/54850/e58a0424-bf02-9e8c-1ad8-7b6bde21f446.png)

各イベントの簡易説明
・青：Loading。読み込み、ネットワークの送受信、HTMLの解析
・黄：Scripting。スクリプトの実行、イベント処理、GC
・紫：Rendering。DOMの変更、ページのレイアウト、描画イベント
・緑：Painting。画像の処理

## 記録方法
左上のRecordボタンを押すと記録が始まる
もう一度押すと記録が停止する

必要に応じて、取得したい情報にチェックを入れておく(JS Profile、Memory、Paint、Screenshots)

## フィルタリング
![Filtering.png](https://qiita-image-store.s3.amazonaws.com/0/54850/633319be-93f3-a584-1a49-93cbde034517.png)

イベントの名前、処理時間、イベントの種類でフィルタリングができます
処理時間でフィルタリングするとその時間以上かかったイベントが表示されるので問題ないか確認してみる

## ビューの切り替え
⑤のところでビューを切り替えることができます

●時系列方式
![FramesView1.png](https://qiita-image-store.s3.amazonaws.com/0/54850/9cd9e770-2e86-ce69-b4b6-08a15984c8c6.png)

●１フレームごとの積み上げ棒グラフ形式
![FramesView2.png](https://qiita-image-store.s3.amazonaws.com/0/54850/63a829e5-fae0-655a-ade7-a21e4d15164e.png)
30FPSのラインを超えている処理がないか確認する

どちらも特定のフレームを選択すると、フレーム内のサマリーが表示される

●簡易モード
![FrameChartView1.png](https://qiita-image-store.s3.amazonaws.com/0/54850/ea099dc6-2c50-c575-53aa-5d29b3a5cc2b.png)
イベントがツリー上に表示されます

●詳細表示モード
![FlameChartView2.png](https://qiita-image-store.s3.amazonaws.com/0/54850/66cabae6-e1f9-75ce-44cb-5b0d335ba44e.png)
ネストされたイベントが下に伸びるように表示され、どの関数がどれぐらいの割合を占めているかわかりやすくなります
下の方にあるのはメインスレッド以外の処理

このモードでは下記の操作ができます
W：ズームイン
S：ズームアウト
AとDで選択範囲を左右に移動

また、画面中央左あたりにある赤いタグですが、処理時間が長すぎたり
イベントにパフォーマンス上の問題がある場合に表示されます

## メモリ使用量の見方
![Memory.png](https://qiita-image-store.s3.amazonaws.com/0/54850/ce9fb206-db10-842d-0736-fec93d914695.png)

赤枠で囲んだ部分がメモリ使用状況グラフ
MEMORYでは、メモリ使用量、イベントリスナー数、要素数が折れ線グラフで表示されます
色付きの□をクリックすると表示/非表示を切り替えられます

## 描画負荷の見方
![Paint.png](https://qiita-image-store.s3.amazonaws.com/0/54850/a5b734c9-d8d4-949f-3bdd-447236a32821.png)

⑧にチェックを入れた状態で計測するとPaintイベントにPaint Profilerタブが追加される
(フィルタでPaintだけにチェックを入れるとわかりやすい)
描画APIごとの所要時間や描画の進捗を時系列で確認できます
描画APIの呼び出しが多すぎないか確認してみる

## スクリーンショット
![Screenshot.png](https://qiita-image-store.s3.amazonaws.com/0/54850/9b630afe-7b52-6b88-ddb6-173b5be7a709.png)

タイムラインの時系列にそったスクリーンショットが表示される

## 計測結果の見方の簡単な説明
例えば、↓この例で言うと
![Summary_Detail.png](https://qiita-image-store.s3.amazonaws.com/0/54850/2b9d8ef1-4682-8e60-3e69-7d1f3880d910.png)

①15ms以上かかってるイベントでフィルタリング
②処理に時間がかかってるイベント(ここではFunction Call)をクリックしてSummaryを見てみると、21.65msかかってる
③Aggregated Details（処理の累積時間）を見ると、sdk.jsのs.create.whenReadyで17.3msかかってる

っていう感じで、どこの処理でどれくらいかかってるかがわかります

# Profilesパネル
CPUやメモリの情報を収集できます
![Profilesパネル.png](https://qiita-image-store.s3.amazonaws.com/0/54850/611b078a-5bf1-583a-b956-36c9827e723a.png)

## 記録方法
調べたい項目をチェックし、計測ボタンを押す

## 計測結果の見方

### Collect JavaScript CPU Profile
![Profiles__CPU.png](https://qiita-image-store.s3.amazonaws.com/0/54850/10417448-e3c8-dd1c-1fe0-47c898689acc.png)
①ビューの切り替え
②関数を選択した後にクリックすると、その関数をルートにして表示
③関数単体のCPU使用率
④関数の中から呼び出している関数の時間も含めたCPU使用率

ビュー
・Chart：フレームチャートとして表示
・Heavy(Bottom Up)：処理時間の大きいもの順で表示
・Tree(Top Down)：実行された関数を読み出しと実行の関係に基づいた構造で表示

Chart表示にすると、↓のように視覚的に確認できる
![Profiles__CPU_Chart.png](https://qiita-image-store.s3.amazonaws.com/0/54850/3d67a860-cd74-0080-0c62-7ef83a91d378.png)
TimelineパネルのChartビューと同じようにWASDで操作できる

### Take Heap Snapshot

#### Summaryビュー
オブジェクトが占めるメモリの総容量を、コンストラクタ名でグルーピングして一覧表示
![Profiles_snapshot_summary.png](https://qiita-image-store.s3.amazonaws.com/0/54850/3e586cfd-37bb-6056-b759-d7b2bd0529c0.png)

各項目の説明
・Constructor：コンストラクタ名。このコンストラクタで作られたオブジェクトを表している
・Distance：オブジェクトのルートからの距離。この値が大きいほど深い参照を保持していることになる
・Objects Count：オブジェクトの数
・Shallow Size：オブジェクト単体のメモリ使用量
・Retained Size：オブジェクトとオブジェクトが参照しているオブジェクトも含めたメモリ使用量

「@数字」はオブジェクトIDを意味する
なぜアドレスじゃないかと言うと、GCが行われるとオブジェクトが移動する(アドレスが変わる)ので
アドレスだと意味をなさないから

オブジェクトを展開して、その中の項目を選択すると
そのオブジェクトがどこから参照されてるかなど詳細を確認できる(Retainersのところ)

上記の例で言うと
Leakコンストラクタを参照しているのが「Retainers」に表示されているleakとselfのオブジェクト
さらに、leakはオブジェクトID、＠156711、＠156453のfunctionに参照されている
その関数はどれかと言うと、同じオレンジ枠で囲んだHTMLButtonElementに定義された関数
という感じに読み取れる

Retainersパネルに何も表示されていない場合は、どこからも参照されてないということなのでGCの対象

#### Comparisonビュー
スナップショットを複数取得すると、スナップショット間の差分を比較できます
![Profiles_snapshot_comparison.png](https://qiita-image-store.s3.amazonaws.com/0/54850/0d77f9ce-c581-5d1b-169a-568b0eaeda71.png)

各項目の説明
・# New：新規オブジェクト
・# Deleted：削除オブジェクト
・# Delta：差分カウント
・Alloc. Size：割り当てられたメモリサイズ
・Freed Size：解放されたメモリサイズ
・Size Delta：差分メモリサイズ

スナップショットを取得した時点で正しく増減しているか確認する

#### Containmentビュー
グローバルに存在するオブジェクトをツリー構造で表示
![Profiles_snapshot_containment.png](https://qiita-image-store.s3.amazonaws.com/0/54850/df58fa7a-fff8-6333-c17d-1cd6df525307.png)

↓こんなこともあるそうです
[Heap Snapshotを使った時のsetIntervalの罠](http://blog.h13i32maru.jp/entry/2014/01/04/104013)

#### Statisticsビュー
ヒープ領域全体に占めるJavaScriptの各データを円グラフで表示
![Statistics.png](https://qiita-image-store.s3.amazonaws.com/0/54850/56ec10cd-2d3b-3a59-fb34-efe202a30f97.png)

### Record Heap Allocations
ヒープ領域のタイムライン
![RecordHeapAllocations_timeline.png](https://qiita-image-store.s3.amazonaws.com/0/54850/c37ba8f4-143a-da4c-64b7-75fff8a1a914.png)
見方はスナップショットと同様（Summaryビューにメモリのグラフが表示されるのが違う点）
色付きのバーは計測中に確保されたメモリ
グレーはGCによって解放されたメモリ

# パフォーマンスの計測・改善

## 簡易診断
まずは細かい計測・改善をする前にAuditsを使った簡易診断を行ってみる

![Audits.png](https://qiita-image-store.s3.amazonaws.com/0/54850/10dc3db6-e508-6fde-8ca7-8f63ea28d860.png)
Runボタンをクリックすると診断開始

診断結果
![Audits_診断結果.png](https://qiita-image-store.s3.amazonaws.com/0/54850/95e4bef4-cef5-5701-24c1-1b8f65b1a29e.png)

### Network Utilizationについての診断解説
**Combine external CSS**
　読み込むCSSファイルの数が多いので、いくつかのファイルにまとめる提案

**Combine external JavaScript**
　読み込むJavaScriptファイルの数が多いので、いくつかのファイルにまとめる提案

**Enable gzip compression**
　gzipで圧縮し転送量を削減する提案
　圧縮可能なファイルの一覧が表示される
　ただし圧縮と展開によるオーバーヘッドが生じることを考慮すること

**Leverage browser caching**
　キャッシュが有効でなかったり、有効期限設定がないリソースの一覧が表示される

**Leverage proxy caching**
　キャッシュは有効でも、”Cache-Control: public”を指定していないリソースの一覧が表示される

**Minimize cookie size**
　このページにおけるクッキーのサイズを示し警告している
　サイズが小さい場合でもこの警告は出る

**Parallelize downloads across hostnames**
　複数のホストを使いリクエストを分散させると、並列ダウンロードでパフォーマンスが向上するというもの

**Serve static content from a cookieless domain**
　CSSや画像などクッキーを必要としないリソースの場合、クッキーレスの別ドメインにそれらのリソースファイルを配置することで
　その分クッキーサイズを小さくできるということ

**Specify image dimensions**
　img要素にwidthとheightが指定されていないと警告が出る
　それらを指定することで、ブラウザ処理に無駄がなくなりページ表示速度を改善できる

### Web Page Performanceについての診断解説
**Optimize the order of styles and scripts**
　CSSファイルとスクリプトファイルの、記述順序を変えたほうが良いという提案
　スクリプトファイルはbodyの閉じタグの直前に置くといい

**Remove unused CSS rules**
　このページで使用していないCSSルールを列挙してくれる
　ただし別ページで使用しているルールもありますので、むやみに削除しない

**Use normal CSS property names instead of vendor-prefixed ones**
　ベンダープレフィックスなしの正式な構文があるのに、ベンダープレフィックス付きプロパティしか書かれていないという警告

## ダウンロード編
### 調査方法
NetworkパネルでSizeカラムやTimeカラム、TimelineカラムのTotal Durationでソートしてみる

↓これはTimelineカラムのTotal Durationでソートした例
![パフォーマンス_ダウンロード.png](https://qiita-image-store.s3.amazonaws.com/0/54850/9dfe2e25-97cc-a627-fb43-8b289e5ff6b1.png)

棒グラフの薄い部分はLatency（ネットワーク接続開始からレスポンス最初の１バイトがブラウザに到達するまでの時間）
　　　　　濃い部分はContent Download（レスポンスの受信開始から完了までの時間）

### 改善方法
圧縮がかかっているかチェック
→テキストなら難読化、gzipなど。画像なら色数を減らす、サイズを小さくするなど

## 待機時間編
待機時間は静的なファイルを返却している場合は短い
動的なデータを返却している場合は、サーバで多くの処理が行われるため長くなる可能性がある

### 調査方法
NetworkパネルでTimelineカラムのLatencyでソートして、Waiting(TTFB)が長いものを調べる

![パフォーマンス_待機時間.png](https://qiita-image-store.s3.amazonaws.com/0/54850/377584da-1455-caad-59ff-64e85d54f119.png)

### 改善方法
サーバ処理を最適化する
または、リクエスト結果をブラウザストレージにキャッシュして、サーバへのリクエストを減らすのも有効

## レンダリング（レイアウト算出）編
### 調査方法
TimelineパネルでFrames Viewの棒グラフで高いところを探す

サンプルソース
https://googlesamples.github.io/web-fundamentals/samples/tools/chrome-devtools/profile/rendering-tools/forcedsync.html
![ForcedSynchronousLayout.png](https://qiita-image-store.s3.amazonaws.com/0/54850/d0097512-758c-eebe-6e1c-737c9d7335ce.png)
黄色の警告マークのイベントを開くと、さらにLayoutで警告が出てるのがわかる
この場合、Forced synchronous layoutという現象が発生している
サンプルソースのようにレイアウト情報の参照と更新が交互に繰り替えされて「Forced synchronous layout 」が頻発する
Forced synchronous layoutはJavaScriptで要素のレイアウトに関するプロパティを参照した時に発生する

↓この辺も参考に
http://tokkono.cute.coocan.jp/blog/slow/index.php/web-technology/reflow-and-repaint-in-browser/

### 改善方法
参照と更新のタイミングをまとめてしまえばOK

## レンダリング（スタイル評価）編

サンプルソース
http://image.gihyo.co.jp/assets/files/magazine/wdpress/2015/89/WDB89-toku1-DevTools.zip

### 調査方法
TimelineパネルでPaintingにかかってるフレームを探す
さらに特定のPaintイベントの中を覗いてペイント処理の詳細を調査

![StylePeformance1.png](https://qiita-image-store.s3.amazonaws.com/0/54850/0df4124c-18fb-fd25-08da-3866bd163fc2.png)
↑background-colorのみ

![StylePeformance2.png](https://qiita-image-store.s3.amazonaws.com/0/54850/4ad5f6e7-074c-41bf-6b5f-a09c2350d12e.png)
↑background(radial-gradient)、border-radius、box-shadow

### 改善方法
適用するスタイルの種類を変える（デザインと相談）

## メモリ編
### 調査方法
①Take Heap Snapshotでスナップショットを複数取って比較してみる

例えば、下記コードを実行し比較してみると、Detached DOM treeというのが表示されます

```js
window.detached = document.createElement('div');
document.documentElement.appendChild(detached);
document.documentElement.removeChild(detached);
for (var i = 0; i < 1000; i++) {
  detached.appendChild(document.createElement('div'));
}
```
![DetachedDOMtree.png](https://qiita-image-store.s3.amazonaws.com/0/54850/69fb91a6-4166-9319-b62c-0e471d5a7942.png)

「Detached DOM tree」はDOMツリーに存在しないけど、存在しているDOM要素なので
メモリとしては残ってます

②TimelineパネルでMemoryを表示してみてイベントリスナ等の推移を確認してみる
![HeapSnapshotListener.png](https://qiita-image-store.s3.amazonaws.com/0/54850/7e0df333-3de1-f12f-45ca-7b33a6d170d0.png)
こんな感じで増え続けていないかチェック

③タスクマネージャ
Chromeにもタスクマネージャが用意されています
Shift+Escでウィンドウが開きます
![タスクマネージャー.png](https://qiita-image-store.s3.amazonaws.com/0/54850/70bccf1c-9581-e574-0b65-6491ab0e6038.png)

ウィンドウ上で右クリックすると項目メニューが表示されるので確認したい項目を選択する
JavaScriptメモリを選択してメモリが増え続けないかチェックしてみる

### 改善方法
①の場合は、`detached = null;`をfor文の後に入れると解放されます

```js
window.detached = document.createElement('div');
document.documentElement.appendChild(detached);
document.documentElement.removeChild(detached);
for (var i = 0; i < 1000; i++) {
  detached.appendChild(document.createElement('div'));
}
detached = null;
```

②の場合は、①と同様に不要になったら削除する

```js
var button = document.getElementById('button');
button.addEventListner('click', function __click() {
  button.removeEventListener('click', __click);
}, false);
```

リスナだけでなくタイマーも削除すること

# 参考文献
WEB+DB PRESS Vol.89の「［詳解］Chrome Developer Tools Web開発を加速する！」を参考にさせて頂きました