じゃあさ、いつメモ化したらいいか具体的に言語化できんの?
はじめに
数年前は私はメモ化警察でした。
「はーい、メモ化してなーい!」
脳死でメモ化してましたが、本当にメモ化って必要なのか考えるようになり、明確な答えを持たないまま、レビュー時にもメモ化が必要かという問いに「パフォーマンスに問題が出たら」という便利な言葉を使用し、逃げていました。
それはもう尻尾を巻いて。
そしてメモ化警察を退職しました。
煽りタイトルですが、いつメモ化すればいいかメモ化の基準とメモ化するかどうかの判断方法について、個人的に考察した考察記事となります。
今回はアニメーションなどなく、zen.devさんのようなWEBサービスを想定します。
この記事で取り扱わないこと
- Reactの書き方
- メモ化の仕方
- 仮想DOMによる差分レンダリング
- Core Web Vitalsに関して
メモ化の基準
早速結論ですが、レンダリング時に以下に当てはまる場合メモ化を検討するというのが私の結論です。
- api通信を省き100ms以上かかる可能性がある
- 余分な再レンダリングが行われている
100ms以上かかる可能性があるというのはユーザーのマシンスペックやpc/mobileでも多少スピードが違ってくるからです。
なぜ100msなのか
なぜ100msなのかを説明するために画面描画が遅くなる仕組みについて説明して行きたいと思います。
画面描画が遅くなる仕組み
メモ化しないことでパフォーマンスに問題出る場合、基本的には画面描画が遅くなります。
- フォーム入力の場合、入力しても画面の反映が遅い
- 動きがスムーズではなく動きがカクカクする
- 初回レンダリング時、上から順にカクカク描画されていっている
- レンダリング時ちらつきが起こる
などなど
では、何がどうなったら画面描画が遅くなるのか。
これにはまず、fpsという画面描画で使用される単位があり、こちらから説明していきます。
fpsとは?
fpsとはFrames Per Secondの略で、1秒間に何回画面が更新されるかを表す単位となります。
例えば、60fpsは1秒間に60回の画面更新が行われます。
1秒間に60回の画面更新なので1回約0.0167秒(16.7ms)となります。
基本的なディスプレイやモニターは60Hz(1秒間に60回更新する)で動作しているため、60fpsが最高となっています。
ゲームなどでは144Hzのモニターなどを利用するようです。
監視カメラや防犯カメラなどは5fpsです。
カクカクしてますね。
画面描画が遅くなるということはfpsが下がっている状態となります。
ではどれぐらいで人間は遅く感じるのかというと30fpsとなります。
30fpsというと1回の描画に約33ms程となります。
アニメーションなどでは60fpsを維持することを目指すのが良いとされています。
MDNにも記載されています。
ではWEBサービスで画面描画が遅いと感じるのはどれぐらいか。
ここではapiの実行時間などは考慮しません。
これは100msぐらいが基準になってくるかと思います。
100msというと約10fpsとなります。
MDNにも100ミリ秒以内、できれば50ミリ秒以内と記載されています。
ですが、個人的には150msも100msも50msもさほど変わらず、影響があるほどではないかと思います。
150msや200msであれば言われてみれば遅い、という感覚でしょうか。
300msとなればちらつきなどでてくるかと思います。
目見にえてパフォーマンスに問題が出てから対応した場合、コンポーネントの数もそこそこあるはずなので、そこそこ作業も大変です。
なので、メモ化は100msぐらいから検討し始めるのがいいのではないか。ということです。
ただし、画面要件によって基準はかなり変わってくるかと思います。
例えば、
- 100msかかるが、これ以上今後追加要素がない画面
- CSRかSSR
- 1msにこだわらないといけない画面
あたりでも変わってくるかと思います。
要は脳死でメモ化する/しないとしてなければいいかと思っています。
useMemoに関してもReactの公式ページで1ms以上であればメモ化する意味があるかもしれないと記載があるが、1msかかる処理であってもその画面に1つだけであればuseMemoは使用しなくてもいいと思います。
逆に0.7msの処理がいくつもある場合、useMemoは使用した方がいいかと思います。
ちなみにuseMemoのオーバーヘッドは0.00017ms程度のようです。
参考: そこのお前! 余計なuseMemo1個に含まれるオーバーヘッドは余計なdiv 0.57個分だぜ!
ある程度の基準を持ち、画面要件によって変えていくのがいいかと思います。
メモ化する判断方法
次にメモ化する/しないの判定方法ですが、まず画面描画の仕組みについて説明して行きます。
画面描画の仕組み
1秒間に60回の画面更新で60fpsですが、1回1回の画面更新の際にどのようなことが起きているのかを説明して行きます。
Reactにおいて1回の画面描画は以下のプロセスを踏みます。
ここでは各プロセスの詳細は説明しません。
詳しくはMDNに載っています。
- babelによって変換されたコードをブラウザが読み込む
例えば以下のコード
export default function Hello() {
const name = 'shun'
return (
<div>
Hello<span>{name}</span>
</div>
)
こちらをbabelによってトランスパイルすると以下のようになる
function Hello() {
let name = 'shun';
return React.createElement("div", {
children: [
"Hello",
React.createElement("span", null, name),
],
});
}
これをブラウザは読み込み各HTML要素を生成する。
(Next.jsやRemixのSSRはサーバーサイドでReact.createElementを実行し、ブラウザでは次の2. DOMツリーの構築から実行されます)
-
DOMツリーの構築
javascriptを実行し、1のReact.createElementによって生成された要素からDOMツリーを構築する -
CSSOMツリーの構築
cssを読み込みCSSOMツリーを構築する -
レンダーツリーの構築
レイアウト計算するため、DOMツリーとCSSOMツリーは組み合わされ、レンダーツリーを構築する -
レイアウト計算
4で構築されたレンダーツリーをもとにノードのサイズとポジションを決める -
paint
レイアウトに従って、要素を画面に描画する
というプロセスを踏みます。
ここまで理解すれば計測し、メモ化するかどうか判断できるかと思います。
では実際に計測し、メモ化するかどうかの判断方法を説明して行きます。
計測方法
今回はフォーム入力による測定とします。
- Chrome DevtoolsのPerformanceタブをクリック
- Recordをクリック(初回レンダリングの計測はRecordの横のリロードをクリック)
- フォーム入力
- STOPをクリック
計測結果が出ました。
Scripting: 8ms
Rendering: 10ms
Painting: 9ms
合計27msとなります。
zen.devさんの記事検索フォームの入力ですが爆速ですね。
もし100ms以上かかっていた場合、再レンダリングが画面上のどこで行われているかを見て行きます。
再レンダリングしている箇所の特定
- Chromeの拡張機能のReact Developer Toolsを追加する
- Chrome DevtoolsのComponentsタブを開く
- 歯車アイコンをクリック
- Highlight updates when components render.にチェックを入れる
これでフォーム入力すると再レンダリングされている箇所がわかります。
もし変更されない箇所なのに再レンダリングされている箇所がある場合、メモ化は有効となります。
メモ化するかしないかは実際に測定し、判断するのがいいかと思います。
以上、メモ化する基準、メモ化する判断方法でした。
これ以外にもメモリ使用量などで判断したりすることも必要かと思いますが、ほとんどのケースはメモ化する基準を設け、上記の測定方法で脳死しないメモ化ができるのではないかと思います。
そもそもメモ化よりもレイアウトシフトであったり、バンドルサイズを減らす方が重要であり、もうすぐReact Forgetがきそうですね。
すでにInstagramでは使用されているようです。
ここの情報が間違っている。もっとこうした方がいい。などあれば遠慮なく教えていただければ幸いです。
Discussion