「アルゴリズムを改善し、DBクエリも高速化した。ベンチマークの数値は完璧。…なのに、ユーザーレビューには『なんだか重い』『反応が悪い』の文字が…。」
アプリケーション開発に携わる中で、そんな悔しい経験はありませんか?
私も昔、コードの実行速度やfps,レスポンスタイムといった 定量的 な指標を求められていました。
報告を求められる側は定量的な指標の方が「ダメじゃん」とか「ちゃんと速度出てるじゃん!」とレビューしやすいのでやむを得ないことではあります・・・
しかし、その努力が必ずしもユーザーの満足度に直結するとは限りません。
「絶対的な処理速度 = ユーザー満足度」は必ずしも成り立つとは限りません。
本当に重要なのは、数値上の速さではなく、ユーザーがどう感じるか、すなわち 「体感パフォーマンス」 です。
※ここで言いたいのは、各モジュールの処理速度改善自体を否定しているわけではなく、UX(ユーザー体験)の必要条件であって十分条件ではないということです。
体感パフォーマンスを上げるためには、総体的に負荷が上がっても「ユーザーの体感」に注力するポイントがあります。
1. UIを止めるな: メインスレッドを解放
まず基本中の基本として
ユーザーがアプリケーションを「応答しない」と感じる最大の原因は、メインスレッド(UIスレッド)のブロッキングです。
UI,UIと言いますがUser Interfaceという名の通り、ユーザーとの接点のためのスレッドです。
メインスreadは、画面の描画やユーザーからのタップ、スクロールといったイベント処理を担当する、いわばアプリケーションの「接客係」です。この接客係が、時間のかかる重い処理(ネットワーク通信、データベースアクセス、大きなファイルの読み書きなど)にかかりきりになってしまうと、どうなるでしょうか?
ユーザーからのあらゆる操作を受け付けられなくなり、画面は完全にフリーズします。AndroidではANR (Application Not Responding)、iOSやWebでも同様のフリーズが発生し、ユーザー体験を致命的に損ないます。
// やってはいけない例 (メインスレッドでデータベースアクセス等の重い処理)
button.addEventListener('click', () => {
// UIが固まる!
const data = heavyDatabaseQuery();
updateUI(data);
});
// こうしよう (非同期処理)
button.addEventListener('click', async () => {
// すぐにローディング表示などを出す
showLoadingSpinner();
// 重い処理はバックグラウンドへ
const data = await heavyDatabaseQueryAsync();
updateUI(data);
hideLoadingSpinner();
});
イメージ的にはコールセンターのお客様窓口が電話連絡を受けて用件を聞いたら「専門部署につなぐので後日連絡します」というイメージですね。
接客係が電話受けながら専門部署につないで解決策聞いて回答するまで該当の顧客と電話をつなぎっぱ・・・だったらそのコールセンターはあっというまにパツンパツンになってしまうと思います。
✅ Tip:
重い処理は必ずメインスレッドから切り離し、非同期で実行しましょう。これは、快適なアプリケーションを作る上での大原則です。
2. 0.1秒の知覚: 「即時フィードバック」
ここからは「総体的な処理は増える」けど「体感パフォーマンス」をあげるTipsです。
人間が物事の因果関係を「瞬時に起きた」と感じられる時間は、おおよそ0.1秒までと言われています。
ユーザーがボタンをタップしてから0.1秒以内に何らかの変化が起きれば、ユーザーは「自分の操作によって、システムが正しく反応した」と直感的に認識します。逆に、それ以上無反応だと「あれ、タップできてない?」「アプリが固まった?」という不安を抱き始めます。
処理自体に数秒かかるとしても、まずは0.1秒以内に最初の反応を返すことが極めて重要です。
具体的なフィードバック例
- ボタンの状態変化: タップした瞬間にボタンを少しへこませたり、色を変えたりする。
- ローディングインジケーター: スピナーやプログレスバーを表示し、「今、頑張って処理しています」と伝える。
- スケルトンスクリーン: YouTubeやFacebookのように、コンテンツの読み込みが始まる前に、そのページのレイアウト枠(スケルトン)を先に表示する。これは、単なるスピナーよりも「もうすぐ表示される」という期待感を与え、体感的な待ち時間を短縮する効果が高いと言われています。
✅ Tip:
処理を開始する前に、まず「あなたの操作を受け付けました」というサインを即座に返しましょう。
もちろんソフト全体としての処理の負荷は増えます。しかし、職人エンジニアが「●●の全体Opsは●●だ!」というのはユーザーにはどうでもよく「なんか反応せんかい!」の方が重要です。
3. 見えるものから届ける: プログレッシブ・レンダリング
私は外食するとき、料理全部完成を待つより「とりあえず先にサラダ食いてぇなー」とか思ってしまうタイプなのですがどうでしょうか。
まぁ、これはタイプにもよるかもしれないのであんまりいい例じゃなかったですが・・・「とりまできたものから渡して!」という経験自体はあるんじゃないでしょうか。
アプリケーションも同じです。画面に表示するすべてのデータが揃うのを待ってから表示するのではなく、準備ができたものから順次表示していくのが「プログレッシブ・レンダリング」の考え方です。
特に、最初に表示されるビュー(ファーストビュー)をとにかく速く表示し、スクロールしないと見えない部分のコンテンツ(大量のリストや画像など)は後から読み込む 「遅延読み込み(Lazy Loading)」 は非常に効果的です。
これにより、ユーザーはすぐにコンテンツの一部を操作し始めることができ、残りの読み込みも「バックグラウンドで進んでいる」と認識するため、体感的な待ち時間が大幅に削減されます。
具体的な手法
- 画像の遅延読み込み: 画面内に表示されるまで画像の読み込みを開始しない。
- 無限スクロール: 画面の末尾までスクロールされたら、次のアイテム群を非同期で読み込む。
✅ Tip:
すべてを一度に提供しようとせず、ユーザーが今まさに見ている部分を最優先で表示しましょう。これにより、たとえ全体のデータ量が多くても、ユーザーは「速い」と感じてくれます。
4. 待ち時間を"体験"に変える演出: トランジション
それでも画面遷移時などに待ち時間が存在してしまうのは、仕方のないことです。
ここで活躍するのが、**トランジション(画面遷移アニメーション)**です。
画面から画面へ、状態から状態へと移り変わる際に、フェードイン・アウトやスライドアニメーションを挟むことを考えてみてください。このわずかなアニメーションが、バックグラウンドでデータを読み込むための時間を自然に稼いでくれます。
また、トランジション自体がカッコよかったらそれ自体がUX向上につながりますよね。
✅ Tip:
意味のある、洗練されたアニメーションは、アプリケーションの品質感を高めると同時に、避けられない待ち時間をユーザーに意識させない強力な武器になります。
ただし、過剰で無意味なアニメーションは逆にストレスになるため、あくまで「ユーザー体験を補助する」という目的を忘れないようにしましょう。
5. FPSを"下げる"
パフォーマンスを議論する際、「高いFPSを出す」というのは当然の話題です。
しかし、ユーザーの体感が最も損なわれるのは、パフォーマンスが急激に低下する瞬間、すなわち 「カクつき(Jank)」 が発生した時です。
人間の知覚は、一定の状態よりも「変化」に敏感です。
常に60fpsを維持している状態から一瞬だけ10fpsに落ちる、という体験は、常に安定して30fpsを維持している状態よりも、遥かに不快に感じられます。
Rendering Performance - Google for Developers
このカクつきは、予期せぬGC(ガベージコレクション)の発生や、スクロール中の描画処理の複雑化など、様々な要因で引き起こされます。
アプリレイヤだけでは難しいですが、組み込みの場合ミドル/ドライバ層であえて「FPSを下げる」というのも手です。
基本的に60fpsを保つためにはメインスレッドの1回の処理は16msに修めなければなりませんが、50fpsなら20ms、30fpsなら33msと猶予時間が伸びます。
✅ Tip:
最高のパフォーマンスを瞬間的に出すことだけを目指すのではなく、パフォーマンスの谷をなくし、いかに安定させるかに注力しましょう。オブジェクトプーリングでGCの頻度を下げたり、リスト表示の要素をシンプルに保ったりと、処理負荷を平準化させることが、真に「滑らか」な体験につながります。
他にも・・・
基本的に考え方は「処理が増えたとしても(総体的なパフォーマンスは悪化したとしても)ユーザーにどう見えるか」です。
上記にあげたTipsもケースによっては逆効果になることがあるので「ユーザーにどう見えるか」を考えて適用する必要があります。
- データ圧縮する(リソース最適化)
展開の処理は増える。がネットワーク等のI/Oがボトルネックになる場合は有効 - 楽観的UI / Optimistic UI
サーバーからの成功応答を待たずに、ユーザーのアクションが成功したものとして、まずUIを更新してしまう手法
99%成功するようなユースケースや失敗してもそこまでユーザー不利益がないケースならば有効
「いいね」を押したときなんかがコレ - リソースの事前読み込み / Pre-loading
ユーザーが次に行う可能性が高い操作を予測し、必要となるリソースを事前にバックグラウンドで読み込んでおく手法。
こちらもケースによっては負荷を与えてしまうので、代表的なユーザー操作などの分析が必要。 - SPA(Single Page Application)の採用
ウェブアプリの話になりますが、ページ遷移ごとにウェブアクセスするのではなくUIに関しては初回アクセス時に全部取ってきちゃうタイプ。データアクセスだけfetch。初回アクセスが重くなるのでケースバイケース。