なぜパフォーマンスを気にするべきなのか?
フロントエンドの仕事をしていると、ユーザーから大小さまざまなパフォーマンスの問題をいつも指摘されます。「ウェブはいつもアプリより遅い」という通説があるおかげで、ウェブはネイティブアプリの開発に比べて、常により多くの最適化作業を必要としています。
サーバーサイドレンダリング(SSR)で開発していた頃は、今よりも問題から自由だったと記憶しています。ページがどれだけ重くても、メモリ使用量がどれだけ増えても、いまユーザーが見ている画面さえなんとか乗り切れば、ページは完全にリロードされるので、問題を大きく心配したり急いで直したりする必要はありませんでした。
しかし今では、多くのウェブページがクライアントサイドレンダリング(CSR)で実装されてユーザーに提供されています。そしてそれは次第にアプリケーション(シングルページアプリケーション、SPA)へと進化し、ブラウザで最初に読み込んだたった一つのページで、さまざまな仮想ページやポップアップを表示し、数えきれないほどの機能までユーザーに提供しています。
ところが、こうして実装されたウェブアプリで起きるパフォーマンスの問題は、サーバーサイドレンダリングとは違ってユーザーにとって致命的です。リロードされないという特性上、アプリを使っている間ずっとユーザー体験全体が損なわれかねないからです。だからこそ、パフォーマンスの問題を解決することは、以前にも増して重要だと考えています。
この記事では、私がフロントエンド開発者としてさまざまなパフォーマンスの問題を経験しながら、今の私が考えるパフォーマンスの定義と問題へのアプローチを紹介します。そして最後に、パフォーマンスの問題を解決するためのツールである Chrome DevTools の利点について簡単に触れます。気軽に読んでいただければ幸いです。
パフォーマンスの定義
「ポップアップが開くまでに時間がかかりすぎます!」
ユーザーがこう言ったら、パフォーマンスの意味はわからなくても、パフォーマンスが問題だということは察しがつくでしょう。開発者はポップアップを呼び出すコードを見て、重複したコードをリファクタリングし、非効率なループを改善したうえで、もう一度ポップアップを開いて改善されたかを確認します。
しかし、何も改善されていません。console.time API で測ると、はっきりとした性能向上が見えます。200ms かかっていた処理が 20ms と 10 分の 1 にまで減ったのに、実際にポップアップを開いても体感できる違いはありません。
これは、私が初めてパフォーマンスの問題にぶつかったときのアプローチの例です。この方法にはいくつも問題がありますが、私が犯したいちばん大きなミスは、問題の原因をブラウザの JavaScript コードだけに限定してしまったことです。
結局、問題はサーバーがデータベースに情報を要求する処理を修正して解決しました。つまり、そもそも JavaScript コードの性能問題は、全体の処理のごく一部にすぎなかったのです。
上の画像は、ブラウザがポップアップに必要なデータを要求し、それを受け取って画面に表示するまでの流れを、時間とともに簡単に表したものです。私がテストして改善していたのは、画像の最後の部分である「サーバーから受け取ったデータを画面に表示する処理」ただひとつでした。
パフォーマンスの単純な辞書的な意味は「単位時間あたりの処理量」です。この言葉は使われる分野によって少しずつ違う意味に解釈されます。私は上の問題に取り組んだとき、パフォーマンスの意味をとても単純に 「JavaScript エンジンでの単位時間あたりの処理量」 と受け取り、間違った方法で問題にアプローチしていました。
その後に感じたのは、フロントエンドではパフォーマンスという言葉をもっと広くとらえるべきだということです。上の例でいえば、ユーザーがポップアップを開くボタンを押した瞬間からのすべての過程を含められる意味でなければなりません。そこで私は個人的に、パフォーマンスを次のように定義しています。
ユーザーの操作が発生した時点から、最終的な結果をユーザーに見せるまでの単位時間あたりの処理量
この定義で全体の過程を見れば、問題の原因になっている部分を正確に切り分けられると考えています。この問題以来、私は フロントエンドで意味のある性能向上が期待できる場合にだけ手を入れる ようにしています。
ブラウザの問題だと確実なら
パフォーマンスを広くとらえたうえで、フロントエンドがパフォーマンス問題の主な原因だと結論づけたなら、ここからは JavaScript コードを修正していきます。しかし、リファクタリングもしてループも大幅に減らしたのに、いっこうに性能がよくなる気配がありません。これを簡単に再現した次の例を見てみましょう。
const originArray = new Array(5000).fill(1);
const $app = document.getElementById('app');
console.time('1つ目の方法');
var array = originArray.map((v, i) => i);
var sum = array.map((i) => i + $app.offsetHeight);
console.timeEnd('1つ目の方法');
このコードで console.time API に挟まれた 2 行を改善するなら、まずはループを効果的に減らせる次の方法を試すでしょう。
console.time('2つ目の方法');
var sum = originArray.map((v, i) => i + $app.offsetHeight);
console.timeEnd('2つ目の方法');
コンソールで計測時間を確認すると、差はあまりなく、修正したコードのほうがかえって時間がかかる場合さえあることがわかります。今度は次のコードを適用して、すべてのケースの計測時間をまとめて比べてみましょう。
console.time('3つ目の方法');
var offsetHeight = $app.offsetHeight;
var array = originArray.map((v, i) => i);
var sum = array.map((i) => i + offsetHeight);
console.timeEnd('3つ目の方法');

結果を見ると、ループは改善せず、むしろコードを 1 行追加したにもかかわらず、元とは比べものにならないほど性能が向上したことがわかります。
💡 このテストは リンク から実際に試せます。
この実験で疑問に思える点は 2 つあります。
- JavaScript の配列は一般的な配列ではなくループに弱いはずなのに、2 つ目の方法に効果がなかったこと
- offsetHeight を先に変数に入れただけなのに、大幅に性能が向上したこと
これらの疑問への答えは次のとおりです。
- 現代の JavaScript エンジンは、型とメモリサイズが同じデータだけを配列に入れる場合、インデックスでの走査が速い密な配列(Dense Array)のように動くよう最適化されているため
- offsetHeight は画面に描画された DOM 要素にアクセスして測った高さを取得するもので、読み取りがとても遅いプロパティであるため
上のコードがなぜ速いのかを知っているか、理解できるかどうかは、この記事では重要ではありません。この例のポイントは、ブラウザごとの動作の特性や最適化アルゴリズム、API ごとの性能に関する知識をすべて覚えるのはかなり骨が折れるということ、そして 目に見えるコードの効率だけを改善するのは、性能改善としての実効性がとても低い方法 だということです。
上の画像は、よく見かけるブラウザのハイレベルな構造の図です。現代のブラウザは、コードの複雑さがすでにオペレーティングシステムと同じくらいだとも言われます。私も含め、こうしたブラウザの構造や動作原理をすべて覚えて理解している開発者は多くないと思います。
こうした理論的な知識が重要ではないとは言えませんが、いますぐ解決しなければならないユーザーのパフォーマンス問題を前にして、深い知識の沼にむやみに飛び込むわけにもいきません。
そこで、こうした問題を知識が完全でなくても素早く解決できるように、自分の代わりに正確にパフォーマンスを評価し、問題の原因を示してくれるツール が必要だと感じるようになります。
結局、ツールが必要だ
先に述べたように、パフォーマンスの問題が起きたとき、開発者がコードを見ただけで正確に原因を突き止めるのは簡単なことではありません。検索して見つかる記事も、ほとんどはよくある問題しか扱っていないので、自分が直面している問題に合った解決事例を見つけるのも難しいです。
結局は自分でなんとか問題を診断して解決しなければならないのですが、それを助けてくれるパフォーマンス評価・診断ツールが世の中にはたくさん出ています。その中でいちばん有名で、無料で、しかもほとんどの PC にすでに入っているツールが Chrome DevTools です。


Chrome DevTools はすでによく使っていると思いますが、「パフォーマンス」 と 「メモリ」 タブを使った経験はおそらく少ないでしょう。パフォーマンスタブは現在のサイトの性能を評価し、どこでどれだけ遅延が発生しているかを視覚的に確認できるようにしてくれます。メモリタブは、メモリに保持されている情報を視覚的に確認できるようにしてくれます。
このツールで性能を評価したデータをもとに、開発者はネットワーク通信や特定の要素のレンダリングなど、性能問題が起きている区間を診断できます。Chrome のデバッグモードとうまく組み合わせれば、自分のコードの特定の部分にかかった時間まで正確に測ることができます。


さらにうれしいのは、Chrome が認識できる問題については、リンクで原因と解決方法まで先に示してくれることです。今の私は、ほとんどのパフォーマンス問題をまず Chrome DevTools で評価し、評価したデータをもとに客観的に問題へアプローチして解決する方法をいちばん好んでいます。
以前は知識をもとに問題の解決方法を選ばなければなりませんでしたが、今はデータをもとに、問題に必要な知識だけをその都度身につけていく方法をとれるので、より速く効率的なアプローチだと思います。この記事が、パフォーマンスへの理解とツールの必要性について役立つものになれば幸いです。
これで記事を終わります。私の経験がほかの方の役にも立てばうれしいです。次回は、実際に開発者ツールでパフォーマンスの問題を評価し、分析していく過程を書く予定です。
