カスタム速度 API

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

Alex Danilo

優れたユーザー エクスペリエンスを実現するには、高パフォーマンスのウェブ アプリケーションが不可欠です。ウェブ アプリケーションの複雑さが増すなか、魅力的なエクスペリエンスを実現するには、パフォーマンスへの影響を把握することが不可欠です。過去数年間に、ネットワークのパフォーマンスや読み込み時間などを分析するためのさまざまな API がブラウザに登場しましたが、これらの API は、アプリケーションの速度低下原因を特定するのに十分な柔軟性と詳細を提供するとは限りません。ここで、User Timing API を入力します。この API は、ウェブ アプリケーションを計測可能にして、アプリケーションが処理を行っている場所を特定するためのメカニズムを提供します。この記事では、API の概要と使用例を紹介します。

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

遅いウェブ アプリケーションを高速化する最初のステップは、時間の消費場所を特定することです。JavaScript コードの領域の時間的な影響を測定することは、ホットスポットを特定する理想的な方法です。これは、パフォーマンスを改善する方法を見つける最初のステップです。幸い、User Timing API を使用すると、JavaScript のさまざまな部分に API 呼び出しを挿入して、最適化に役立つ詳細なタイミング データを抽出できます。

高解像度の時間と now()

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

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

var myTime = window.performance.now();

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

DOMHighResTimeStamp 型

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

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

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

User Timing インターフェースには、ハンゼルとグレーテルのパンくずリストのような形で時間の消費状況を追跡できる、アプリ内のさまざまな場所でメソッドを呼び出せる関数が用意されています。

mark() の使用

タイミング分析ツールキットの主なツールは mark() メソッドです。mark() はタイムスタンプを保存します。mark() の非常に便利な点は、タイムスタンプに名前を付けられることです。API は、名前とタイムスタンプを 1 つの単位として記憶します。

アプリケーション内のさまざまな場所で mark() を呼び出すと、ウェブ アプリケーションでその「マーク」に到達するまでにかかった時間を計算できます。

この仕様では、mark_fully_loadedmark_fully_visiblemark_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');

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

作成した measure を削除することもできます。これに対応する clearMeasures() というメソッドを使用します。clearMarks() とまったく同じように動作しますが、測定値に対してのみ動作します。たとえば、次のコードです。

window.performance.clearMeasures('measure_load_from_dom');

は、上記の measure() の例で作成した測定値を削除します。すべての測定値を削除する場合は、引数なしで clearMeasures() を呼び出すだけなので、clearMarks() と同じように機能します。

タイミング データを取得する

マーカーを設定したり、区間を測定したりすることは良いことですが、ある時点でそのタイミング データを取得して分析を行う必要があります。これも非常にシンプルで、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 の概要を把握できたので、この API を使用して、ウェブ アプリケーションですべての XMLHttpRequest にかかる時間を分析できます。

まず、すべての 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 を今すぐ試して、処理速度を上げる方法を見つけましょう。ユーザーは、ユーザー エクスペリエンスが大幅に向上したことに感謝するでしょう。