カスタム速度 API

ウェブアプリについて理解する

Alex Danilo

優れたユーザー エクスペリエンスを実現するには、高パフォーマンスのウェブ アプリケーションが不可欠です。ウェブ アプリケーションが複雑になるにつれて、パフォーマンスへの影響を理解することが、魅力的なエクスペリエンスを構築するうえで重要になります。過去数年間で、ネットワークのパフォーマンスや読み込み時間などの分析に役立つさまざまな API がブラウザに登場しましたが、アプリケーションの速度低下の原因を特定するのに十分な柔軟性を備えた詳細な情報は必ずしも提供されません。User Timing API を入力します。この API は、ウェブ アプリケーションを計測して、アプリケーションがどこで時間を費やしているかを特定するために使用できるメカニズムを提供します。この記事では、API とその使用例について説明します。

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

ウェブ アプリケーションの速度を上げるための最初のステップは、どこに時間がかかっているかを把握することです。JavaScript コードの領域の時間への影響を測定することは、ホットスポットを特定するための理想的な方法であり、パフォーマンスを改善する方法を見つけるための最初のステップです。幸いなことに、User Timing API を使用すると、JavaScript のさまざまな部分に API 呼び出しを挿入し、最適化に役立つ詳細なカスタム速度データを抽出できます。

高解像度時間と now()

正確な時間測定の基本は精度です。以前はミリ秒単位の測定に基づいてタイミングを計っていましたが、これは問題ありません。しかし、ジャンクのない 60 FPS のサイトを構築するには、各フレームを 16 ミリ秒で描画する必要があります。そのため、ミリ秒単位の精度しかない場合、適切な分析に必要な精度が不足します。そこで登場するのが、最新のブラウザに組み込まれている新しいタイミング タイプである高解像度時間です。High Resolution Time では、マイクロ秒の解像度まで正確な浮動小数点タイムスタンプが提供されます。これは以前の 1,000 倍の精度です。

ウェブ アプリケーションで現在の時刻を取得するには、Performance インターフェースの拡張機能を形成する now() メソッドを呼び出します。次のコードに、その方法を示します。

var myTime = window.performance.now();

PerformanceTiming という別のインターフェースもあります。これは、ウェブ アプリケーションの読み込み方法に関連するさまざまな時間を提供します。now() メソッドは、PerformanceTiming の navigationStart 時間が発生してからの経過時間を返します。

DOMHighResTimeStamp 型

以前、ウェブ アプリケーションの時間を測定しようとしたときは、DOMTimeStamp を返す Date.now() のようなものを使用していました。DOMTimeStamp は、ミリ秒単位の整数値を返します。高解像度時間に必要な高い精度を提供するために、DOMHighResTimeStamp という新しいタイプが導入されました。このタイプは浮動小数点値で、ミリ秒単位の時刻も返します。ただし、浮動小数点であるため、値はミリ秒の小数部を表すことができ、1000 分の 1 ミリ秒の精度が得られます。

カスタム速度のインターフェース

高解像度のタイムスタンプを取得できたので、カスタム速度 インターフェースを使用してタイミング情報を取得しましょう。

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');

ウェブ アプリケーション全体に名前付きマークを設定することで、大量のタイミング データを収集し、都合の良いときに分析して、アプリケーションがいつ何を行っているかを把握できます。

measure() を使用して測定値を計算する

タイミング マークをいくつか設定したら、それらの間の経過時間を調べます。これを行うには、measure() メソッドを使用します。

measure() メソッドは、マーク間の経過時間を計算します。また、マークと PerformanceTiming インターフェースの既知のイベント名の間の時間を測定することもできます。

たとえば、次のようなコードを使用して、DOM が完了してからアプリケーションの状態が完全に読み込まれるまでの時間を計算できます。

window.performance.measure('measure_load_from_dom', 'domComplete', 'mark_fully_loaded');

measure() を呼び出すと、設定したマークとは別に結果が保存されるため、後で取得できます。アプリケーションの実行中に時間を保存することで、アプリケーションの応答性を維持できます。また、アプリケーションがいくつかの処理を完了した後にすべてのデータをダンプして、後で分析できるようにすることもできます。

clearMarks() を使用してマークを破棄する

設定したマークをまとめて削除できると便利な場合があります。たとえば、ウェブ アプリケーションでバッチ実行を行うため、実行ごとに新しく開始したい場合などです。

clearMarks() を呼び出すことで、設定したマークを簡単に削除できます。

以下のサンプルコードでは、既存のマークをすべて削除して、必要に応じてタイミング実行を再度設定できるようにしています。

window.performance.clearMarks();

もちろん、すべてのマークをクリアしたくない場合もあります。特定のマークを削除したい場合は、削除したいマークの名前を渡します。たとえば、次のコードがあるとします。

window.performance.clearMarks('mark_fully_loaded');

は、最初の例で設定したマークを削除し、設定した他のマークは変更せずに残します。

作成したメジャーを削除したい場合は、clearMeasures() という対応するメソッドを使用します。clearMarks() とまったく同じように動作しますが、作成した測定値に対して動作します。たとえば、次のコードがあるとします。

window.performance.clearMeasures('measure_load_from_dom');

は、上記の measure() の例で作成したメジャーを削除します。すべての測定値を削除する場合は、clearMarks() と同様に、引数なしで clearMeasures() を呼び出します。

タイミング データの取得

マークを設定して間隔を測定するのはよいことですが、ある時点で、そのタイミング データを取得して分析を行う必要があります。これも非常に簡単で、PerformanceTimeline インターフェースを使用するだけです。

たとえば、getEntriesByType() メソッドを使用すると、すべてのマーク時間またはすべての測定時間をリストとして取得できるため、リストを反復処理してデータを処理できます。このリストは時系列で返されるため、ウェブ アプリケーションでヒットした順にマークを確認できます。

以下のコードは、

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

は、ウェブ アプリケーションでヒットしたすべてのマークのリストを返します。一方、次のコードは、

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

作成したすべてのメジャーのリストが返されます。

また、エントリに付けた特定の名前を使用して、エントリのリストを取得することもできます。たとえば、次のコードがあるとします。

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

は、startTime プロパティに「mark_fully_loaded」タイムスタンプを含む 1 つの項目を含むリストを返します。

XHR リクエストのタイミング(例)

User Timing API の概要を把握できたので、これを使用して、ウェブ アプリケーションですべての XMLHttpRequests にかかる時間を分析できます。

まず、すべての send() リクエストを変更して、マークを設定する関数呼び出しを発行します。同時に、別のマークを設定する関数呼び出しで成功コールバックを変更し、リクエストにかかった時間の測定値を生成します。

通常、XMLHttpRequest は次のようになります。

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

この例では、リクエスト数を追跡するためのグローバル カウンタを追加し、それを使用して、行われた各リクエストの測定値を保存します。これを行うコードは次のようになります。

var reqCnt = 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 ごとに一意の名前値を持つメジャーを生成します。リクエストは順番に実行されると想定しています。並列リクエストのコードは、順序どおりに返されないリクエストを処理するために、もう少し複雑にする必要があります。これは読者の課題として残しておきます。

ウェブ アプリケーションが大量のリクエストを処理したら、以下のコードを使用して、それらのリクエストをすべてコンソールにダンプできます。

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 には、ウェブ アプリケーションのあらゆる側面に適用できる優れたツールが多数用意されています。ウェブ アプリケーション全体に API 呼び出しを散りばめ、生成されたタイミング データを後処理して、時間が費やされている場所を明確に把握することで、アプリケーションのホットスポットを簡単に絞り込むことができます。しかし、ブラウザがこの API をサポートしていない場合はどうすればよいでしょうか。問題ありません。こちらで、API を適切にエミュレートし、webpagetest.org とも連携する優れたポリフィルを見つけることができます。この機会にぜひご登録ください。今すぐアプリケーションで User Timing API を試してみてください。アプリケーションを高速化する方法がわかり、ユーザー エクスペリエンスが大幅に向上するため、ユーザーに感謝されることでしょう。