カスタム速度 API

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

Alex Danilo

高パフォーマンスのウェブ アプリケーションは、優れたユーザー エクスペリエンスに不可欠です。ウェブ アプリケーションの複雑さが増すにつれ、魅力的なエクスペリエンスを生み出すために、パフォーマンスへの影響を理解することが不可欠です。ここ数年、ネットワークのパフォーマンスや読み込み時間などの分析に役立つさまざまな API がブラウザに登場しましたが、必ずしも、アプリケーションの動作を低下させている要因を見つけるのに十分な柔軟性があり、詳細を把握できるわけではありません。User Timing API を入力します。これは、ウェブ アプリケーションを計測可能にして、アプリケーションが時間を費やしている場所を特定するために使用できるメカニズムです。この記事では、この API について説明し、その使用例を紹介します。

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

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

高解像度の時間と now()

正確な時間測定の基本は精度です。以前は、ミリ秒単位で計測するタイミングでも問題ありませんが、ジャンクのない 60 FPS のサイトを構築するには、各フレームを 16 ms で描画する必要があります。したがって、ミリ秒の精度しかなければ、優れた分析に必要な精度が得られません。[高解像度の時間] を入力します。これは、最新のブラウザに組み込まれている新しい速度タイプです。[High Resolution Time] から、マイクロ秒単位の精度を実現できる浮動小数点タイムスタンプが得られます。これは、以前の 1,000 倍向上しています。

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

var myTime = window.performance.now();

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

DOMHighResTimeStamp 型

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

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

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

カスタム速度インターフェースには、アプリケーションのさまざまな場所でメソッドを呼び出せる関数が用意されています。Hansel および Gretel スタイルのパンくずリストを表示して、どこで時間を費やしているかを追跡できます。

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.peformance.clearMarks('mark_fully_loaded');

最初の例で設定したマークが削除され、他のマークは変更されないままになります。

作成した measure を削除することもできます。これに対応する clearMeasures() メソッドがあります。これは clearMarks() とまったく同じように機能しますが、代わりに行った測定に基づいて機能します。たとえば、コードは次のようになります。

window.performance.clearMeasures('measure_load_from_dom');

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

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

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

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

次のコード:

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

このコードでは、ウェブ アプリケーションでヒットしたすべてのマークのリストが返されます。

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

作成したすべての measure のリストが返されます。

ユーザーに付けた名前を使用して、エントリのリストを取得することもできます。たとえば、コードは次のようになります。

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

このクエリは、startTime プロパティの「mark_Fully_loading」タイムスタンプを含む 1 つのアイテムを含むリストを返します。

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

User Timing API の全体像が整ったので、この API を使用して、すべての XMLHttpRequests がウェブ アプリケーションにかかる時間を分析できます。

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

したがって、通常、XMLHttpRequest は次のようになります。

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

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

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 ごとに一意の name 値を持つ measure を生成します。ここでは、リクエストが順番で実行されると仮定します。順不同で返されるリクエストを処理するには、並列リクエストのコードをもう少し複雑にする必要があります。これは読者向けの演習として残します。

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

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 を試してみましょう。アプリケーションを高速化する方法を学べば、ユーザー エクスペリエンスが大幅に改善されたことに感謝するでしょう。