2016年12月8日木曜日

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

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

Networkパネル

Networkパネルはページのリクエストをしてからの通信内容の一覧を表示します

記録方法

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

必要に応じて、Disable cacheCapture screenshotsを設定する

表示項目の変更

赤枠で囲んだ部分を右クリックすると

こんな感じでメニューが出てくるので表示したい項目をクリックする

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

Timelineをクリックするとソートの種類を選べます

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

フィルタリング


赤枠で囲んだ部分にフィルタリングのキーワードを入力すると、条件に合うものだけ表示される

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

リクエストの詳細

リクエストを選択しTimingタブをクリックすると詳細が表示される(Timelineにマウスを合わせてもOK)

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

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

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

HAR(HTTP Archive)

Networkパネルのウィンドウ上で右クリックすると、このようなメニューが出てきます

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

HTTP Archive Viewerで同じように解析できます

Timelineパネル

Timelineパネルではブラウザで起こった各種処理を時系列に記録しボトルネックがないか調査できます

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

記録方法

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

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

フィルタリング

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

ビューの切り替え

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

時系列方式

1フレームごとの積み上げ棒グラフ形式

30FPS
のラインを超えている処理がないか確認する

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

簡易モード

イベントがツリー上に表示されます

詳細表示モード

ネストされたイベントが下に伸びるように表示され、どの関数がどれぐらいの割合を占めているかわかりやすくなります
下の方にあるのはメインスレッド以外の処理

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

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

メモリ使用量の見方

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

描画負荷の見方

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

スクリーンショット

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

計測結果の見方の簡単な説明

例えば、この例で言うと

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

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

Profilesパネル

CPUやメモリの情報を収集できます

記録方法

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

計測結果の見方

Collect JavaScript CPU Profile


ビューの切り替え
関数を選択した後にクリックすると、その関数をルートにして表示
関数単体のCPU使用率
関数の中から呼び出している関数の時間も含めたCPU使用率

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

Chart表示にすると、のように視覚的に確認できる

Timeline
パネルのChartビューと同じようにWASDで操作できる

Take Heap Snapshot

Summaryビュー

オブジェクトが占めるメモリの総容量を、コンストラクタ名でグルーピングして一覧表示

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

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

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

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

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

Comparisonビュー

スナップショットを複数取得すると、スナップショット間の差分を比較できます

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

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

Containmentビュー

グローバルに存在するオブジェクトをツリー構造で表示

こんなこともあるそうです
Heap Snapshotを使った時のsetIntervalの罠

Statisticsビュー

ヒープ領域全体に占めるJavaScriptの各データを円グラフで表示

Record Heap Allocations

ヒープ領域のタイムライン

見方はスナップショットと同様(Summaryビューにメモリのグラフが表示されるのが違う点)
色付きのバーは計測中に確保されたメモリ
グレーはGCによって解放されたメモリ

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

簡易診断

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


Run
ボタンをクリックすると診断開始

診断結果

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要素にwidthheightが指定されていないと警告が出る
 それらを指定することで、ブラウザ処理に無駄がなくなりページ表示速度を改善できる

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でソートした例

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

改善方法

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

待機時間編

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

調査方法

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

改善方法

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

レンダリング(レイアウト算出)編

調査方法

TimelineパネルでFrames Viewの棒グラフで高いところを探す

サンプルソース
https://googlesamples.github.io/web-fundamentals/samples/tools/chrome-devtools/profile/rendering-tools/forcedsync.html

黄色の警告マークのイベントを開くと、さらに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イベントの中を覗いてペイント処理の詳細を調査


↑background-color
のみ


↑background(radial-gradient)
border-radiusbox-shadow

改善方法

適用するスタイルの種類を変える(デザインと相談)

メモリ編

調査方法

Take Heap Snapshotでスナップショットを複数取って比較してみる

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

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 DOM tree」はDOMツリーに存在しないけど、存在しているDOM要素なので
メモリとしては残ってます

TimelineパネルでMemoryを表示してみてイベントリスナ等の推移を確認してみる

こんな感じで増え続けていないかチェック

タスクマネージャ
Chrome
にもタスクマネージャが用意されています
Shift+Esc
でウィンドウが開きます

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

改善方法

の場合は、detached = null;for文の後に入れると解放されます

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;

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

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

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

参考文献

WEB+DB PRESS Vol.89の「[詳解]Chrome Developer Tools Web開発を加速する!」を参考にさせて頂きました

 

0 件のコメント:

コメントを投稿