動作の遅いアプリや、応答性の悪いアプリを好む人はいません。ユーザーは、タッチ、タップ、クリック、ジェスチャ、キーの押下に即座に反応するアプリを求めています。アニメーションがスムーズに動き、音楽やビデオの再生、一時停止、再開を軽快に行うことができ、ユーザーの操作にアプリが追いつくのを待つ必要もない、そんな動作をユーザーは期待しています。この記事は、あなたのアプリを "高速で滑らか" にする方法を紹介するシリーズの第 1 回です。

エンジニアリング チームでは、Metro スタイル アプリのパフォーマンス確保の方法について、長期間にわたり検討されました。その過程で私たちは、高速で滑らかなパフォーマンスを実現するためにプラットフォームで何ができるかを見いだし、優れたエクスペリエンスを提供するアプリの構築において何が有効で何が有効でないかも理解しました。このブログでは、ユーザーにとって最良のエクスペリエンスを皆さんが構築するために役立つと思われる私たち自身が経験した苦いレッスンの一部をお伝えします。

パフォーマンスの心理学

パフォーマンスは、単にストップウォッチによる時間計測と効率的なアルゴリズムだけで決まるものではありません。パフォーマンスについて考えるとき、私は全体的な視点に立って、ユーザーがアプリの使用中にどのように時間が経過するかという点を考慮するようにしています。高速で滑らかとは、アプリにとって何を意味するのでしょうか。1 つの考え方として、ユーザーのエクスペリエンスを、知覚、許容、および応答性という 3 つのカテゴリに分類することができます。

知覚: いつになっても終わらない

知覚は、"高速で滑らか" のうちの "高速" の認識にかかわります。パフォーマンスに対するユーザーの知覚は、アプリ内でなんらかの作業を実行するためにかかった時間の長さが、ユーザーにとってどの程度好意的に感じられたかという点で定義されます。ユーザーの認識が現実と一致するのが理想的ですが、実際には、ユーザーに認識される時間の方が現実よりも重要である場合が多いのです。インストールが自動的に進んで終了することを期待して席を離れたのに、戻ってきたら途中で停止していて、あなたの応答を待つメッセージが表示されていた経験はありませんか。

1 つの処理について思い出されるステップが多いほど、速度は遅く感じられるものです。

するべきこと:

  • 作業完了までにユーザーが実行する必要のある各アクティビティの間隔を短くする
  • ユーザーに質問する必要がある場合は必ず、すべての質問を前もってたずねるようにし、また他に質問がないことを明確にする。

してはいけないこと:

  • ユーザー アクティビティの間になんらかの処理時間が発生し、アクティビティが複数の期間に分断される。

許容: 楽しい時間は早く過ぎる

許容は、"高速で滑らか" のうちの "高速" と "滑らか" の両方にかかわります。知覚が経過時間に対するユーザーの記憶を測るものだとしたら、許容とは、その時間の経過がどの程度好意的に受け入れられるかを測る尺度と言えます。

操作の完了までにかかる時間がわからなければ、待ち時間は苦痛になるものです。たとえば、写真を編集するアプリを使っているとしましょう。フィルターを適用するコマンドをクリックしたら、アプリが応答しなくなりました。フリーズしたままで時間が過ぎていくと、たとえそれがほんの数秒だったとしても、すぐに我慢の限界に達してしまいます。

フォト アプリでは、フィルターを適用中であることを示す進捗状況バーや小さいアニメーションを追加して、この問題を解消しています。不明瞭なまま放置されるわけではないので、操作の完了までの待ち時間が長くなってもユーザーは許容してくれます。アプリは高速かつ滑らかに感じられるため、大きな違いになります。

するべきこと:

  • アプリの中で、読み込みに長い時間 (1 秒以上) がかかる部分を特定する。
  • 特定されたシナリオにおいて、ユーザーにとって先が見えない状況を取り除くか少なくする。
  • 処理の進行状況と、完了までの予想時間をユーザーが視覚的に確認できるようにする。
  • UI スレッドがブロックされ、アプリがフリーズしたように見えるのを防ぐために、非同期 API を使用する。

してはいけないこと:

  • ユーザーにフィードバックを提供せずに長時間処理を実行し続ける。

応答性: 反射 < 反応 < 応答

応答性は、"高速で滑らか" のうちの "滑らか" の認識にかかわります。許容が時間に対する期待度と好感度を測るものだとしたら、応答性とは、時間に対する期待度は実行中のアクティビティに関連するという概念です。アクティビティのパフォーマンスを計測して評価するには、比較の基準となる時間間隔を定義する必要があります。ここでは、このような時間間隔を "インタラクション クラス" と呼ぶことにします。私たちは社内で、以下のインタラクション クラスを使用して、Windows 全体の中心的なシナリオに対する応答性の目標を定義し、目標達成の妨げとなる問題をバグとして追跡しています。

インタラクション クラス

目標

上限

ユーザーの知覚

代表的なシナリオ

瞬時

50 ミリ秒以下

100 ミリ秒

気付くほどの遅延はない。

入力応答 - マウスのクリック、ボタンのタップなど

高速

50 ~ 100 ミリ秒

200 ミリ秒

遅延に気付く可能性はあるが、最小限。フィードバックは不要。

パン/スクロール

標準

100 ~ 300 ミリ秒

500 ミリ秒

すばやいが、高速というほどではない。フィードバックは不要。

ページ内ダイアログの表示 (ヒント情報、ポップアップ、フライ アウト、トーストなど)

応答的

300 ~ 500 ミリ秒

1 秒

高速ではないが、すぐに応答するように感じられる。フィードバックは不要。

新しいページへの移動、ズーム、処理を施したデータや体裁を整えたデータの表示

継続的

500 ミリ秒超

10 秒

中程度の待ち時間があり、すぐに応答するとは感じられない。他の作業ができるほど長くはない。場合によってはフィードバックが必要。

アプリの起動、アプリのスナップ、エラー メッセージ、タイムアウト、進行状況の表示の更新

延長的

5 秒超

1 分超

待ち時間に他の作業ができるほど長い。場合によってはフィードバックが必要。

デバイス/ライブラリの同期やインデックス作成

するべきこと:

  • アプリにとって重要な各シナリオを、それぞれにふさわしいエクスペリエンスを表すインタラクション クラスに割り当てる。
  • 各シナリオが目標を満たしているかどうかを見極め、最適化する。
  • 各シナリオについて、ユーザーへのフィードバックが必要かどうかを判断し、必要な場合は提供する。

最適化のキーとなるエクスペリエンス

パフォーマンスの問題はさまざまな形で現れます。バッテリ寿命が短くなったり、パンとスクロールがユーザーの指の動きに追いつかなくなったり、場合によってはアプリが長時間フリーズしたように見えることもあります。しかしもちろん、パフォーマンスの改善ばかりに無限の時間を費やすことはできません。そこでこのセクションでは、最適化の効果が最もよく現れる部分を特定するためのヒントと、最適化の実行に役立つガイドラインを紹介します。

最適化の基準の 1 つとして、ユーザーが違いを認識するには、処理速度が 20% 増減する必要があるということが言われています。

現在の速度が 20% 増減すると違いが認識されることを示す図

場合によっては、アプリ内のわかりやすい場所に問題が見つかって、重要なシナリオのパフォーマンスを簡単に向上できることもあります。しかし、そのような目立つ問題を修正した後は、処理時間を即座に 20% も短縮できるようなパフォーマンスの問題が見つかる可能性は大幅に低下します。多くの場合は、もっと小さい問題が同じパスに積み重なって、1 つのユーザー エクスペリエンスの問題を形成しているものです。次のセクションでは、このようなパスを発見するための便利なツールについてお話しします。

アプリのプロファイル

最適化の効果が最も期待できる場所を特定する方法として、一番簡単ですぐに利用できるのは、アプリのプロファイルを実行することです。プロファイルでは、アプリがさまざまな機能に費やした時間のデータを集めることができます。この結果から、アプリで最も時間のかかっているホット スポットを特定できます。さいわいなことに、Visual Studio 11 には Windows 8 Consumer Preview で利用できる便利なプロファイル ツールが用意されています。

作業に着手する前に、Windows 8 が動作するデバイスは多岐にわたるという点に注意してください。性能の良いハードウェアでパフォーマンスを計測しても、そのデータは、他のフォーム ファクターでのパフォーマンス特性を正確に表すとは限りません。私が自身の機能のパフォーマンスを計測するときには、性能の低いノート PC を使うようにしています。詳細については、ブログ記事「Consumer Preview を実行する: 推奨システム構成」を参照してください。

計測のためにコンピューターを準備するには、次の手順を実行します。

  1. コンピューターが電源に接続されていて、バッテリで動作しているのではないことを確認します。多くのシステムでは、バッテリ使用時は消費電力を抑えるために、通常と異なる動作になります
  2. 計測の実行にはリモート デスクトップを使用しないようにします。リモート デスクトップでは、ハードウェア アクセラレーションが無効になり、正しい結果が得られない可能性があります。
  3. システムのメモリ使用率の合計が 50% 未満であることを確認します。それよりも使用率が高い場合は、不要なアプリを閉じて 50% を下回るようにします。これは、他のプロセスによる影響を抑え、目的のアプリによる影響を正しく計測するためです。

アプリのプロファイルを開始するには、次の手順を実行します。

  1. Visual Studio からアプリを起動します。
  2. [Debug] (デバッグ) メニューで、以下の 2 つのパフォーマンス分析オプションのどちらかを選択します。
  3. メニューに [Start Performance Analysis] (パフォーマンス分析の開始) (Alt+F2) と [Start Performance Analysis Paused] (パフォーマンス分析を一時停止状態で開始) (Ctrl+Alt+F2) が表示されている

    • [Start Performance Analysis] (パフォーマンス分析の開始): すぐに利用状況情報の記録を開始し、アプリを起動します。
    • [Start Performance Analysis Paused] (パフォーマンス分析を一時停止状態で開始): 後から情報の記録を再開できる状態でアプリを起動します。このオプションは、情報を記録する前にアプリを特定の状態にする場合 (特定のユーザー シナリオをテストする場合など) に使用します。
  4. 計測する操作を実行したら、Visual Studio に戻って [Stop profiling] (プロファイルの停止) をクリックします。VS によってレポートが生成され、アプリのアクティビティに関する主要な情報が表示されます。

Visual Studio の [Stop profiling] (プロファイルの停止) リンク

パフォーマンス レポートを参照するときに特に役立つビューは、コール ツリー ビューと関数の詳細ビューの 2 つです。コール ツリー ビューでは、実行中にアプリから呼び出されたすべての関数と、それぞれの呼び出し回数、それぞれの実行にかかった時間を確認できます。[Expand Hot Path] (ホット パスの展開) というボタンもあり、これをクリックすると、実行に最も長い時間のかかった関数が表示されます。この影響が最も大きいと考えられるため、最適化を行う際には、こうした関数に集中して取り組む必要があります。

コール ツリー ビューの [Expand Hot Path] (ホット パスの展開) ボタン

レポートには、各関数の包括時間と排他時間の両方が示されます。排他時間とは、その関数内のコードの実行にかかった時間の割合 (%) を表します。包括時間とは、関数が呼び出されてから戻るまでの時間の割合 (%) を表します。つまり、その関数内のコードの実行にかかった時間だけでなく、その関数から呼び出された任意の関数内のコードを実行するためにかかった時間も含まれます。

アプリ内のいずれかの関数について、関数の詳細ビューを開くことができます。このビューには、関数で実行された処理の詳細情報が表示され、その関数で実行された特定のコード、呼び出された関数、それらの関数にかかった時間を確認できます。図の例では、Array.concat 関数がホット パスの主な要因となっており、アプリの実行時間の 29.7% を占めています。関数の詳細ビューを見ると、実際には Array.concat 関数自体が占めている割合は 12.6% に過ぎず (オレンジ色の枠)、大部分の時間は、連結関数の処理の一部として呼び出される get_MainResourceMap 関数に費やされていることがわかります (紫色の枠)。これらの関数のいずれかをクリックすると、さらに詳しい情報を確認できます。

Array.concat の関数の詳細ビューに、呼び出し元関数の割合、現在の関数の割合、呼び出された関数の割合が表示されている。

Windows 8 Consumer Preview のリリースの数週間前、あるアプリを使っていた私は、途中で使用中のノート PC が熱くなり、ファンが回り始めたことに気付きました。私たちは、そのアプリの所有者と協力してプロファイルを実行し、CPU を必要以上に大量に利用するコード パスを特定して修正しました。違いはすぐに実感できるほど大きく、その変更によって、同じアプリの他のシナリオ (スナップなど) における動作も改善されました。

中断を味方に

アプリが起動されるのは、アプリが中断していない場合だけです。中断しているアプリは、再開とほとんど同時に以前の表示状態に戻ります。知覚、許容、そして応答性を管理するテクニックの 1 つとして、アプリを中断状態にする方法があります。こうすることで、ユーザーはよりすばやくアプリにアクセスできるようになり、使用するたびにアプリの読み込みを待つ必要がなくなります。要するに、アプリを終了しないようにすれば、パフォーマンス面で大きな利点になります。これを実現する最も簡単な方法は、中断時のアプリのメモリ使用量を低く抑えることです。

アプリが中断状態に入る前には、必要な処理を実行できるように数秒の時間が与えられます。この時間を利用して、再開時に簡単に再取得できるような大きいオブジェクトはすべて解放してください。これにより、アプリのメモリ フットプリントは小さく抑えられ、他の目的に利用する空き領域が足りなくなったときに、システムによってアプリが終了される可能性も大幅に低くなります。次の方法を使用すると、アプリが適切に中断されるかどうかを確認できます。

  1. アプリを起動し、その後でデスクトップに戻ります。
  2. Ctrl + Shift + Esc キーを押してタスク マネージャーを起動し、[More details] (詳細表示) をクリックして、すべてのオプションを表示します。
  3. [View] (表示) メニューの [Status values] (状態値) をポイントし、[Show suspended status] (中断状態の表示) をクリックします。

[View] (表示) メニューの [Group by type] (種類でグループ化) と [Show suspended status] (中断状態の表示) が選択されている。

数秒後、アプリ名の横に "Suspended" (中断) という言葉が表示されます。中断している間、アプリのプライベート ワーキング セットのメモリ使用量が、実行中の値よりも大幅に低下することを確認してください。中断時のメモリ使用量は、アプリの複雑さと大まかな規模に応じて、次の基準を参考にすることをお勧めします。

アプリの大まかな複雑さ

中断時のプライベート セット (最大)

最小規模のアプリ (Hello World など)

40 ~ 60 MB

中規模のアプリ (天気など)

60 ~ 80 MB

大規模なアプリ (Windows Live Photos など)

120 ~ 150 MB

中断しているアプリのメモリ使用量は、次の方法で確認できます。

  1. 上で説明した手順を使用して、アプリが中断していることを確認します。
  2. タスク マネージャーでアプリ名を右クリックし、[Go to details] (詳細の表示) をクリックします。これにより、アプリのプロセスの詳細が表示されます (JavaScript を使う Metro スタイル アプリはすべて WWAHost.exe というプロセスになり、それ以外はアプリ名がプロセスとして表示されます)。
  3. [Memory (private working set)] (メモリ (プライベート ワーキング セット)) という列が表示されていることを確認します。表示されていない場合は、任意の列を右クリックし、[Select columns] (列の選択) をクリックして、[Memory (private working set)] (メモリ (プライベート ワーキング セット)) をオンにします。
  4. 列名の一覧で [Memory (private working set)] (メモリ (プライベート ワーキング セット)) が選択されている

  5. この列の下に表示される値が、アプリのプライベート ワーキング セットです。


メモリ使用量が 20,736 K として表示されている


アプリ エコシステムの優良な住民となるために

残念に思われるかもしれませんが、ユーザーに使われるアプリはあなたのアプリだけではありません。したがって、ユーザーのシステム上で動作するときは行儀良くふるまうことが大切です。そうしないと、ユーザーは、システムに遅延時間が生じたときにあなたのアプリに疑いを抱き、システムのパフォーマンスの低下やバッテリ寿命の低下の原因があなたのアプリにあるのではないかと感じ始めるかもしれません。ここでは、他のアプリと上手に付き合うためのヒントを紹介します。

メモリ使用量を急増させない

システムの役割は、すべての Metro スタイル アプリのリソース ニーズに対応して調整を行い、ユーザーがリソースを管理する必要をなくすことです。新しいアプリの実行時には、必要なリソースを確保するために、中断しているアプリが自動的に終了されることがあります。この副作用として、いずれかのアプリで大量のメモリを要求すると、他のアプリが終了される場合があります。これは、要求したアプリがそのメモリをすぐに解放したとしても発生する可能性があります。この問題を回避するために、負荷の高い処理に取り組むときは、複数の小さい処理に分けて進めていくことが最良の方法です。

メモリ マネージ言語 (JavaScript や C# など) でコードを記述する場合、メモリの割り当てがいつ発生するかを制御するのはきわめて困難ですが、陥りやすい落とし穴もあります。その 1 つは、現在のシステムの解像度に見合わないほど大きいリソースを読み込み、縮小する場合です。新しい縮小表示 API (英語) を使用すると、対象とする解像度に適したリソースを取得し、必要以上に高い負荷がかかるのを避けることができます。私たちがテスト用に使っている画像ベースのパズル ゲームでは、この方法によってメモリ使用量を大きく削減することができました。

実行時のメモリ使用量については、開発言語にかかわらず、アプリの複雑さと大まかな規模に応じて、次の基準を参考にすることをお勧めします。

アプリの大まかな複雑さ

合計ワーキング セット (最大)

最小規模のアプリ (Hello World など)

50 ~ 70 MB

中規模のアプリ (天気など)

80 ~ 100 MB

大規模なアプリ (フォトなど)

120 ~ 150 MB

実行中のアプリのメモリ使用量は、次の方法で確認できます。

  1. Ctrl + Shift + Esc キーを押してタスク マネージャーを起動し、[More details] (詳細表示) をクリックして、すべてのオプションを表示します。
  2. [Options] (オプション) メニューをクリックし、[Always on top] (常に手前に表示) がオンになっていることを確認します。
  3. アプリを起動します。アプリがタスク マネージャーに表示されたら、そのアプリを右クリックし、[Go to details] (詳細の表示) をクリックします。
  4. [Working set (memory)] (ワーキング セット (メモリ)) という列が表示されていることを確認します。表示されていない場合は、任意の列を右クリックし、[Select columns] (列の選択) をクリックします。[Working set (memory)] (ワーキング セット (メモリ)) 列をオンにします。

    列の一覧で [Working set (memory)] (ワーキング セット (メモリ)) が選択されている

  5. この列の下に表示される値が、アプリの合計ワーキング セットです。

アイドル時やスナップ時はシステム リソースの使用量を最小限に

デバイスのバッテリ寿命は、ユーザーが最も気にする点の 1 つです。このため、どの Metro スタイル アプリでも、ユーザーが活発に操作を行っていないときは使用中のシステム リソースを解放して、電力の節約に貢献することをお勧めします。以下に、リソースの使用量を削減するためのヒントをいくつか紹介します。

スナップ時にはアニメーション、オーディオ、背景ビデオを実行しない

  • スナップされたアプリは、ユーザーの関心の中心からは外れることになります。アプリがきちんと動作していて役に立つと感じられるようにすることも大切ですが、メイン アプリのために用意されたリソースを取り上げたり、バッテリを消費したりしないように注意する必要があります。
  • ここでは、アプリが音楽/ビデオの再生専用クライアントでないことを前提としています。

アニメーション、オーディオ、背景ビデオを無限ループで再生しない

  • ユーザーが一定時間アプリを操作しなかった場合は、これらのアクティビティを一時停止するようにします。ユーザーが再びアプリを操作し始めたら、これらのアクティビティを問題なく再開することができます。
  • 私の経験では、バックグラウンドのアクティビティを一時停止するだけで、アイドル時のアプリの CPU 使用率が 40% から 0% にまで下がるのを確認したことがあります。

スナップ時またはアイドル時には、ファイルのキャッシュ、デバイスの同期、その他のディスクを多用する処理を実行しない

  • 不必要にディスクを使用すると、他の操作のシーク タイムが長くなり、電力が大量に消費されます。

次のステップ

この記事では、パフォーマンスについての考え方、アプリの重要なエクスペリエンスに目標を設定する方法、そして最適化が必要な部分を特定するために役立つツールについて説明しました。次回の私の記事では、最も大きな問題となっている部分を最適化する方法と、よくある落とし穴を避ける方法についてお話しするつもりです。これらのヒントがお役に立てばさいわいです。

次回をお楽しみに。

-- Windows プログラム マネージャー、David Tepper