ウェブアプリについて理解する
優れたユーザー エクスペリエンスを実現するには、高パフォーマンスのウェブ アプリケーションが不可欠です。ウェブ アプリケーションがますます複雑になるにつれ、魅力的なエクスペリエンスを実現するには、パフォーマンスへの影響を理解することが不可欠です。過去数年間に、ネットワークのパフォーマンスや読み込み時間などを分析するために、さまざまな 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()
メソッドは、PerformanceTiming の navigationStart
時間が経過してからの経過時間を返します。
DOMHighResTimeStamp 型
以前は、ウェブ アプリケーションの時間を測定するために、DOMTimeStamp を返す Date.now()
などの関数を使用していました。DOMTimeStamp は、ミリ秒の整数値を値として返します。高解像度時刻に必要な精度を実現するため、DOMHighResTimeStamp という新しい型が導入されました。このタイプは浮動小数点値で、時間もミリ秒単位で返されます。ただし、浮動小数点数であるため、値はミリ秒の小数部分を表すことができ、ミリ秒の 1,000 分の 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()
の例で作成した測定値を削除します。すべての測定値を削除するには、引数なしで 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 を使用して、ウェブ アプリケーションですべての 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 を試して、アプリの高速化方法を探りましょう。ユーザー エクスペリエンスが大幅に向上し、ユーザーに感謝されるはずです。