[優れたUXを目指して]アプリの性能について:第1回

これまでGUIの歴史やUXについて記事を書いてきましたが、どんなに優れたUIや機能を提供してもアプリの動作が遅いと、それ以前の問題となってしまいます。そこで今回はアプリの性能について解説したいと思います。
TEXT: 2020年6月3日 上田恵
▷ 前提
私は業務として証券取引関係のアプリに関わることがあります。それらのアプリでは株価を表示するのですが、株価は秒間何度も値が変わり、かつ同時にたくさん表示する必要があるため、アプリにはそれなりの性能が求められます。
このシリーズではそんなアプリ開発の経験から得た知識について書いてます。
このシリーズではそんなアプリ開発の経験から得た知識について書いてます。
・言語やフレームワークなどで特性はあると思いますが、
なるべく多くのアプリで当てはまることについて書きたいと思います
なるべく多くのアプリで当てはまることについて書きたいと思います
・フロントエンドのクライアントアプリケーションについて書いています
(WEB アプリやスマホアプリのクライアントサイド)
(WEB アプリやスマホアプリのクライアントサイド)
・特定のプラットフォームやフレームワーク固有の問題ではなく、
アプリを作る上で共通的に発生する設計や課題について書いています
・例えば1ミリ秒レベルの性能チューニングで必要なノウハウではなく、
(主観的ではありますが)利用に耐えうる性能にするための
ノウハウを書いています
これから以下のことについて触れていきたいと思います。
今回は 「1. 環境を知る」「2. 実行回数を知る」についてです。
今回は 「1. 環境を知る」「2. 実行回数を知る」についてです。
|
1. 環境を知る
2. 実行回数を知る
3. 描画を知る
4. メモリを知る
5. 処理タイミングを知る
6. 通信を知る
7. ビルドを知る
|
▷ 環境を知る
ここでいう環境とはアプリの実行環境やランタイム、開発に採用するフレームワーク等のことです。Windowsのアプリが.NET Framework/.NET Core(以降、.NET と記載)で作られているのなら、そのバージョンは何なのか。WEBアプリであればどのブラウザで動いているのか、フレームワークを採用してるのであれば何のどのバージョンを使っているのか。基本的には新しいバージョンは性能が向上してるので、バージョンによってアプリの性能は変わってきます。
つまり「このフレームワークが流行ってるから採用」ではなく、最新バージョンの状況や今回作るアプリの特性、チームメンバーのスキルセットを考慮して何を採用するかを決める必要が来ます。
つまり「このフレームワークが流行ってるから採用」ではなく、最新バージョンの状況や今回作るアプリの特性、チームメンバーのスキルセットを考慮して何を採用するかを決める必要が来ます。
そのほか開発経験のあるフレームワークでもバージョンが上がると、性能を向上するための仕組みが追加されていることもあるのでちゃんと知っておく必要があります。例えば、WEBアプリを作る際に利用されるフレームワークで Angular がありますが、執筆当時最新である Angular9ではIvyという機能が追加され大きく性能が向上しています。
また、differential loadingという機能も加わり、レガシーなブラウザ向けとモダンなブラウザ向けでそれぞれ別のJavaScriptを利用するようにし、モダンなブラウザがより一層早く動けるように工夫しています。
ブラウザの話がでましたが、ブラウザによっても性能は大きく変わってきます。サーバーは自分たちで用意したマシンで動きますが、フロントエンドのクライアントアプリケーションはエンドユーザーの実行環境で動きます。つまり、どんな環境で利用されるのかを考えて、許容する最低レベルの環境で動かして性能測定をしておく必要があります。
ブラウザの話に戻りますが、基本的にIE11はChromeなどの他のモダンなブラウザに比べて遅いです。どうしようもありません。WEBアプリを開発する際は、サポート対象のブラウザを確認しておく必要があります。
IE11が含まれているのであれば事前に性能差があることをステークホルダーに伝えておくべきですし、WEB アプリの性能測定もIE11の性能を気にしておく必要があります。場合によってはIE11だけ、例えばアニメーションを省略するなど、処理を変える必要もあるかもしれません。
スマホアプリを開発するのであれば、kotlin/SwiftなどNativeで開発するのか、それともアプリ内ブラウザを使った WEBアプリにするのか、XamarinやReact Nativeなどのクロスプラットフォームの仕組みを使うのか考える必要があります。
一般的にはNativeで作るのが最も性能が良くなると思いますが、その他のクロスプラットフォームな仕組みでも使用に耐えれないほど遅いというわけではなく、十分な性能はでます。繰り返しになりますが、何を採用するかはアプリの特性や生産性、メンバーのスキルセットとの相談になります。「流行ってるから」ではなく、プロトタイピングをしてでも何を選択するべきか考える必要があります。
アプリの性能について考えるとき、アプリの実行環境や技術選定についてよく考えると良いでしょう。
▷実行回数を知る
最近の各種フレームワーク、実行環境は非常に優れているため、なんとなくで書いていてもそれなりの性能になると思いますが、実装が増えていくうちに遅くなっていく経験はないでしょうか。一つの原因として実行回数があります。
生産性を向上するためにリアクティブな機能を使うことがあると思いますが、よくよく考えないと性能劣化を招くことがあります。例えば、いろんなライブラリやフレームワークで computed と呼ばれるような機能が提供されています。これはその関数が依存する変数に変化があったときに再実行してくれる機能です。
私が最初に見かけたのはKnockout.jsでしたが、Vue.jsにもありますし、RxJSでもストリームを合成することで似たような動きは実現できますし、馴染みのある方も多いと思います。便利なのでついつい使ってしまうのですが、次のようなケースを考えてみましょう。
株式ランキング画面を computed の機能を使って実現することを考えてみます。「Yahoo! ファイナンス」の画面を見ると、株式ランキングを構成する要素として「ランキングの種類」「市場」「期間」があるようです。
これらのうちどれか一つでも変わったら、ランキングを取得するという動きを作ってみます。
|
// computed なメソッド
ランキング取得() {
const 種別 = Get選択されたランキング種別();
const 市場 = Get選択された市場();
const 期間 = Get選択された期間();
ランキング取得API呼び出し();
}
|
上記は疑似コードですが、ランキングの種別、市場、期間のいずれかの値が変われば実行されるものとします。3つのうちどれか 1 つでも値が変われば勝手に実行してくれるので非常に便利ですよね。
では、ここで「ランキング種別が変わったら市場と期間をデフォルト値に戻す」という要件があったとします。もしかすると以下のような挙動になるかもしれません。
|
1. ランキング種別を変更する
2. ランキング取得のメソッドが実行される
3. 市場がデフォルト値に設定される
4. ランキング取得のメソッドが実行される
5. 期間がデフォルト値に設定される
6. ランキング取得のメソッドが実行される
|
なんとcmputedなメソッドが3回も実行されてしまいました。その結果、アプリの描画が遅くなるだけでなくサーバーの呼び出し回数が増えてしまうのでサーバーサイドにまで負荷をかけることになります。しかも最終的な表示結果は正しいものになりますので機能テストでは正常と判断されてしまいます。
こういった自動的な再評価機能は非常に便利なのですが実行回数は気にしておきましょう。ロジックをちゃんと考えれば回避できるかもしれませんし、遅延評価の仕組みがフレームワークに準備されているかもしれませんし、なければ debounce/throttle といったイベント間引きの機能の検討をしましょう。
こうした実行回数の問題は他にも、マウスやスクロールの検知イベントなどの処理、.NETのEventHandlerや RxJSのSubscribeなど、イベント駆動の処理を書くと発生する可能性があります。いろんなケースがあるのでイベント駆動の処理を書く際には何回実行されているのか注意してください。
| 性能の話とは直接的には関係ありませんが computed なメソッドは いったいどうやって作られているのでしょうか? そういったことも考え、想像することも重要だと思います。 仕組みが想像できればメリット・デメリットや注意点も見えてきます。 |
実行回数と似たような話で、画面への反映回数もあります。変数の値が変わると自動的に画面を再描画してくれるような仕組みは多くのフレームワークで存在すると思います。描画も同じ話で、一連の処理で1回だけ描画すれば良いところを何度も描画してしまうケースがあります。描画についてはあまり意識しないかもしれませんが、重たい処理なので再描画が繰り返されると性能に影響を与えてしまいます。
例えば、リストを画面に表示しているケースを考えます。このリストには配列要素の変更を検知し、自動的に画面を再描画する機能があったとします。

リストの数の分だけ、画面には表が表示されています。
ではここで、このリストに 100 個の要素を追加します。
ではここで、このリストに 100 個の要素を追加します。
|
for(var i = 0; i < 新しい要素の数; i++) {
画面に表示しているリスト.Add(新しい要素[i]);
}
|
この結果、何が起きるでしょうか?
このリストは100回変更を検知するため、画面は100回再描画されてしまいます。
| 1. リストに要素が追加される 2. リストは要素が追加されたことを検知する 3. 画面を再描画する 4. リストに要素が追加される 5. リストは要素が追加されたことを検知する 6. 画面を再描画する ・ ・ 以下繰り返し |
この場合はどうするべきでしょうか。
よくあるのは、AddではなくAddRangeのように配列を追加するメソッドが用意されてないか確認してください。ない場合は自分で拡張するしかありません。変更通知は .NETであればCollectionChangedやPropertyChangedといった機能で実現されてますので、その通知を抑止することができる自作のCollectionを作成して対応します。
Angularであれば、コンポーネントに対してChangeDetectionStrategy.OnPushを設定して ChangeDetectorRefを利用することで、変更通知を手動にすることができます。さらにZone.jsの仕組みを理解して工夫したり、trackByという機能を使えば効率的に画面の再描画ができる可能性もあります。
この辺りの動きはフレームワークが隠蔽していることもあるので気づくのが難しく、手軽な回避手段が用意されていないこともあります。開発生産性を下げずにどういう仕組みで実現させるのか考えて取り組む必要があります。
●まとめ
今回は実行環境や採用技術などのアプリを取り巻く環境と、フレームワークやライブラリの便利機能がどれだけ実行されているのかを知るべきという話を書かせていただきました。次回は「描画を知る」「メモリを知る」について書く予定です。
今回は実行環境や採用技術などのアプリを取り巻く環境と、フレームワークやライブラリの便利機能がどれだけ実行されているのかを知るべきという話を書かせていただきました。次回は「描画を知る」「メモリを知る」について書く予定です。




