激重Reactコンポーネントが集うグリッドを30倍くらい速くした話
https://gyazo.com/c42f660ef88e277e61b63420cfe06e45
当日お聴きいただいた皆様ありがとうございました。
みなさまこんにちは、mgn901.iconです。前回のRamble vol.2では、カラーパレットを自作するためのツールを開発しているというお話をさせていただきました。palapal(ぱらぱる)というツールで、色相や明度、輝度といったパラメータとステップの数を指定すると、カラーパレットが生成されるというものです。 https://gyazo.com/c17eea8d0b792ccf2c6187026b1bbc18
なんと、今回それが完成しました!まずはこちらの動画をご覧ください。色相のスライダーを操作すると、パレットにもそれがちゃんと反映されています。
https://scrapbox.io/files/6a30c2e074d67ad5ae020fff.mp4
というわけで、今回のLTはこれで終了です。ありがとうございました!……
https://gyazo.com/db2df1806d7eccb3ae5fb04c20ba7a4e
……嘘です。palapalには大きな問題があります。先ほどのデモ動画で、画面の動きがかなりカクついていましたよね。そうです。スライダーを動かすたびに画面が固まってしまうのです。ブラウザにもよりますが、開発ビルドではなんと2fpsしか出ません。開発者体験も最悪です。こんな品質で公開するわけにはいきませんね。
https://gyazo.com/1d4385cc803ff02c4054f68f06ba294c
「開発ビルド」:palapalで用いているUIライブラリのReactには、エンドユーザ向け成果物への同梱が意図されている「プロダクションビルド」と、開発者ツールとの併用が意図されている「開発ビルド」がある。開発ビルドでは、レンダリング中に、専用のプロファイラやデバッガのための処理が行われるので、プロダクションビルドよりも動作が遅い。
今回は、そんなpalapalのパレットの表示を高速化するというお話です。よろしくお願いいたします。
https://gyazo.com/53d96d29fa535d60a5a097f31cd9d1c4
今回palapalで用いているUIライブラリのReactについて簡単に説明しておきましょう。Reactは、値を受け取って「React要素」を返す関数である「コンポーネント」を組み合わせてUIを表現します。そのコンポーネントをReactのレンダラ(react-domのReactRoot.render関数)に渡すことで、DOMツリーが作られます。しかし、それだけではマウスやキーボードの入力に対応できません。そこで、コンポーネントの中で、UIのイベントを受けてpropsに変更をかけたり、それをReactに通知して再レンダリングを要求するようなイベントハンドラを定義してやることで、インタラクティブなUIをつくることができます。今回、スライダーを動かした際に2fpsしか出ないのは、イベントが起こってから再レンダリングが完了するまでの間に、どこかで時間がかかっているからです。 https://gyazo.com/8330dfe255d4384d45a3d0f412ce8e12
注意:Reactは変更に伴う再レンダリング要求を抽象化しているので、開発者自身が明示的に再レンダリング要求のためのコードを書くことはありません。
まず私は、変更の部分に時間がかかっているのでは?と疑って、スライダーで入力されたパラメータから色を計算するのに使っているライブラリを変えることを考えました。
https://gyazo.com/d475c7010c822213034637e745ee70d5
また、スライダーを操作しても、パレットのコンポーネントに入力される値すべてが変わるわけではありません。セルをメモ化(memo (React))することで、再レンダリングを減らすことができると考えました。 https://gyazo.com/658129b39d4b76c35916dd083d0165ce
これら2つの施策は、残念ながら、両方とも、ほとんど効果が出ませんでした。
https://gyazo.com/f8b3008c91a9276946039af02e6c127d
本当の原因は、今回使用しているReact Aria Componentsの表のコンポーネントの挙動にありました。React Aria ComponentsはWAI-ARIAに準拠したWebアプリケーションの実装を手助けしてくれるコンポーネントライブラリです。これが提供する表のコンポーネントが、1つのセルを変更するにも、なぜかすべてのセルを再レンダリングしていたのです。とはいえ、React Aria Componentsは便利なので、これを捨てるという選択はとりたくありませんでした。 https://gyazo.com/5554a96da8ef8bd3b4186bc6a873b647
パレットが変化するときに全部のセルが再レンダリングされてしまうのであれば、スライダーを操作して再レンダリングが完了するまでに係る部分のアーキテクチャを見直すことになります。そこで今回は、上下分離パターンというアーキテクチャを開発しました。スライダーの値から生成されたパレットのデータをそのまま表に渡すと、全てのセルが再レンダリングされてしまいます。だから、パレットのデータを表のコンポーネントに渡すのはやめます。その代わり、パレットのデータの変更箇所に対応した各セルにデータの変更を通知して、セルの中身から再レンダリング要求を出させる仕組みをつくりました。これによって、再レンダリング要求の発生を局所化することができます。 https://gyazo.com/4812c02042c38c96797c8100886671aa
では、上下分離パターンを適用した結果を動画でご覧ください。
https://scrapbox.io/files/6a30c37f74d67ad5ae02119d.mp4https://scrapbox.io/files/6a30c38274d67ad5ae0211a9.mp4
注:画面録画の関係でコマ落ちしている箇所があります。それでも、コマ送りで確認すると60fps出ている箇所があります。
上下分離パターンの適用前は開発ビルドで2fpsしか出なかったのが、適用後は開発ビルドでも60fps近い速度が出るようになりました。約30倍の高速化です。
https://gyazo.com/94bbebe634da32c907bd7fb375629267
今回はアーキテクチャを見直し、Reactの再レンダリング要求が発生する場所を局所化することで、劇的な速度改善を図ることができました。いわゆる小手先のテクニックやチューニングを施したとしても、再レンダリング要求の数を減らせないのであれば、その効果には限界があります。最初から、要件にとって適切なアーキテクチャを選んでおけば、細かいチューニングなどせずともパフォーマンス向上の恩恵に与ることができます。今日のLTはこれで終わりたいと思います。ご清聴ありがとうございました。
https://gyazo.com/8d37c52486ac58be874c86872e609dcb