User Timing API

あなたの Web アプリをもっと理解するために

HTML5 Rocks

Web アプリケーションが高性能であることは、素晴らしいユーザー体験にとって極めて重要なことです。Web アプリケーションが複雑になるにつれて、パフォーマンスに対する影響を理解することは、魅力的な体験を作り出す上で必須になってきました。ここ数年で、ネットワークや、ロードタイムなどのパフォーマンスの解析に役立つ、さまざまな API がブラウザに数多く登場してきました。しかし、それらは何があなたのアプリケーションを遅くしているかを探るに足りる柔軟性を持ったきめ細かな情報を必ずしも教えてくれるわけではありません。User Timing API の登場で、あなたのアプリケーションがどこで時間を使っているかを特定するための仕組みが提供されました。この記事ではAPIとその使い方の例について解説します。

計測できないものは最適化できない

遅い Web アプリケーションを速くする最初のステップは、どこで時間がかかっているかを解明することです。JavaScript のコードの中で時間的な影響を計測することは、ホットスポットを特定するための理想的な方法であり、どのようにパフォーマンスを改善していけばよいかを見つけるための最初のステップです。User Timing API は、JavaScript 内のさまざまな場所に挿入可能なAPIを呼び出す手段を提供し、そして最適化に役立つ詳細なタイミング情報を抽出してくれます。

高精度計時と‘now()

正確な時間計測に重要なのは精密さです。昔はおおよそミリ秒の計測タイミングでも良しとしていましたが、精密に 60FPS を保つサイトを構築するということは、各フレームを 16ms で描画しなければいけないということを意味します。だから、ミリ秒での精度しかないのでは、優れた解析を行うために必要な精密さに欠けます。高精度計時の登場により、新しいタイミングの型がモダンブラウザに搭載されました。高精度計時はマイクロ秒精度の浮動小数点のタイムスタンプを提供してくれます。今までより 1000 倍高い精度です。

Performance インターフェースの拡張である now() メソッドを呼び出すことで、Web アプリケーション内で現在時刻を取得することができます。次のコードでやり方を示します:

var myTime = window.performance.now();

PerformanceTiming と呼ばれる別のインターフェースは、どのように Web アプリケーションが読み込まれたかに関するさまざまな時間情報を数多く提供してくれます。now() メソッドは、PerformanceTimingnavigationStart が発生した時からの経過時間を返します。

DOMHighResTimeStamp 型

従来、Web アプリケーションの時間を計測しようとする場合は、DOMTimeStamp を返す Date.now() のようなものを使っていました。DOMTimeStamp は値としてミリ秒の整数値を返します。高精度計時に必要とされる、より高い精度を提供するために、DOMHighResTimeStamp と呼ばれる新しい型が導入されました。この型は浮動小数点の値であり、ミリ秒で時間を返します。しかし、その値は浮動小数点なので、ミリ秒より小さい値を表現できるため、1/1000 ミリ秒の精度を生成できます。

User Timingインターフェース

高精度なタイムスタンプを利用できるようになったので、タイミング情報を取り出すのに User Timing インターフェースを使いましょう。

User Timing インターフェースは、アプリケーションのさまざまな場所で呼び出すことができて、どこで時間が使われているか追跡するためのヘンゼルとグレーテルのパンくずリストを提供するメソッドを提供してくれます。

mark()’ の使用

mark()’ メソッドはこのタイミング分析ツールキットのメインツールです。mark() はタイムスタンプを保存します。mark() の何がすごく便利かというと、タイムスタンプに名前を付けることができ、API がその名前とタイムスタンプを1つの単位として記憶してくれることです。

アプリケーションのさまざまな場所で mark() を呼ぶことで、そのマークに当たるまでにどのくらい時間が経過したかを算出できます。

仕様では、‘mark_fully_loaded’ 、‘mark_fully_visible’ 、‘mark_above_the_fold’ のような、関心を引く自明な名前をマーク用にいくつか推奨しています。

例えば、アプリケーションが完全に読み込まれたときのマークは次のようなコードで設定できます。

window.performance.mark('mark_fully_loaded');

Web アプリケーション全体を通して名前が付いたマークを設定することで、アプリケーションがいつ何をしているのかを算出するための大量のタイミングデータを収集して、手すきの時間に解析することができます。

measure()’ で測定値の算出

たくさんのタイミングマークを設定すると、マーク間の経過時間を知りたくなるでしょう。‘measure()’ を使ってそれができます。

measure() メソッドはマーク間の経過時間を計算します。そしてあなたが設定したマークと PerformanceTiming インターフェースの周知のイベント名との間の時間も計測できます。

例として、DOM が完成してからアプリケーションが完全に読み込まれるまでの時間は以下のようなコードで計測できます:

window.performance.measure('measure_load_from_dom', 'domComplete', 'mark_fully_loaded');
: この例では PerformanceTiming インターフェースの周知の名前である ‘domComplete’ を渡している。

measure() を呼び出すと、あなたが設定したマークとは独立して結果が保存されるので、後から取り出すことができます。アプリケーションが実行されている間、時間を別に保存することによって、アプリケーションはレスポンスできる状態を継続し、アプリケーションが処理を終えてから全てのデータを取り出すことができるので、後から分析が可能です。

clearMarks()’ でマークの削除

ときには設定したマークを大量に削除できると便利です。例えば、Web アプリケーションを一括実行することもあるでしょうから、それぞれの実行時にマークも真っ新から新規に設定し始めたいと思うでしょう。

clearMarks()’ の呼ぶことで、設定済のマークを削除することは簡単です。

以下に例示したコードは既存マークを全て削除するので、タイミングを再度設定することもできます。

window.performance.clearMarks();

もちろん全てのマークをクリアしたくない場合もあるでしょう。特定のマークだけを削除したいなら、削除したいマークの名前を渡すだけでできます。例えば、以下のコード:

window.peformance.clearMarks('mark_fully_loaded');

は、最初の例で設定したマークは削除しますが、残りのマークに関しては何も変更しません。

同様に計測を削除したいこともあるでしょう。それに対応したメソッドとして 'clearMeasures()' があります。このメソッドは計測を対象にすること以外は clearMarks() と同様に動作します。例えば、コード:

window.performance.clearMeasures('measure_load_from_dom');

は、先述の measure() の例で作成した計測を削除します。全ての計測を削除したいなら、clearMeasures() を引数なしで呼び出すことによって clearMarks() と同様に動作します。

タイミングデータの抽出

マークを設定してそのインターバルを計測するのも有効ですが、場合によっては解析するために必要なタイミングのデータを取得したいことがあります。これもまた、PerformanceTimeline インターフェースを利用することで簡単に行うことができます。

例として、'getEntriesByType()' メソッドで、マークされた時間をすべて取得したり、計測時間をリスト形式で取得できるので、ループを使ってデータを順次処理することができます。素晴らしいことに返されるリストデータは時系列でソートされているので、Web アプリケーションの中でマークが呼び出された順番を知ることができます。

以下のコード:

var items = window.performance.getEntriesByType('mark');

は、Web アプリケーション内で呼び出されたすべてのマークをリストで返すのに対して、以下のコード:

var items = window.performance.getEntriesByType('measure');

は、作成されたすべての計測時間をリストで返します。

マークに付けた特定の名前を使ってエントリーのリストを取得することも可能です。以下のサンプルコード:

var items = window.performance.getEntriesByName('mark_fully_loaded');

は、startTime プロパティーに ‘mark_fully_loaded’ のタイムスタンプを含んだ、アイテムが1つのリストを返します。

XHR リクエストの計測(例)

User Timing API の全体像が見えてきたので、この API を利用して、Web アプリケーションの中で XMLHttpRequests がどれくらいの時間がかかっているか分析できます。

まず始めにすべての send() リクエストでマークを設定し、同時に success コールバックでもう1つのマークを設定するように変更することで、リクエストにかかった時間を計測できるようにします。

そのため、通常 XMLHttpRequest を利用する場合は以下のようになります:

var myReq = new XMLHttpRequest();
myReq.open('GET', url, true);
myReq.onload = function(e) {
  do_something(e.responseText);
}
myReq.send();

ここの例では、グローバルカウンターを追加してリクエストが呼ばれた回数をカウントします。またこのカウンターを利用して、リクエストごとに計測を保存できるようにします。これを実行するコードは以下のようになります:

var reqCount = 0;

var myReq = new XMLHttpRequest();
myReq.open('GET', url, true);
myReq.onload = function(e) {
  window.performance.mark('mark_end_xhr');
  reqCnt++;
  window.performance.measure('measure_xhr_' + reqCnt, 'mark_start_xhr', 'mark_end_xhr');
  do_something(e.responseText);
}
window.performance.mark('mark_start_xhr');
myReq.send();

このコードではすべての XMLHttpRequest に対してユニークな名称で計測を生成します。ここではすべてのリクエストは順次実行されることを想定しています - 並列で実行される処理では、リクエストが正しい順序で終わるとは限らないため、もう少し複雑なハンドリングが必要となります。その対処方法は、この記事を読んでいる皆さんのエクササイズとして残しておきます。

Web アプリケーションからの大量のリクエストが処理されたら、以下のコードを利用することで、結果をコンソールへ出力できます:

var items = window.performance.getEntriesByType('measure');
for (var i = 0; i < items.length(); ++i) {
  var req = items[i];
  console.log('XHR ' + req.name + ' took ' + req.duration + 'ms');
}

まとめ

User Timing API はあなたの Web アプリケーションのあらゆることに適用できる多くの素晴らしいツールを提供します。Web アプリケーションのすみずみで API が呼び出されるように設定し、集計したタイミングデータからどこで時間がかかっているかを解析することで、ホットスポットとなっている箇所を容易に絞ることができます。もし、あなたのブラウザがこの API をサポートしていなくても問題ありません。この API をよくエミュレートしていて、webpagetest.org とも相性が良い素晴らしい polyfill がこちらにあります。グズグズする必要はありません。今すぐあなたのアプリケーションで User Timing API を試してみましょう。処理が遅い部分を見つけ出し改善することで、ユーザー体験が向上し皆喜ぶでしょう。

Comments

0