より遅く、よりスムーズに:React Fiberを扱う







2017年9月16日、React Fiberがリリースされました-ライブラリの新しいメジャーバージョンです。 ここで読むことができる新しい機能を追加することに加えて、開発者はライブラリコアのアーキテクチャを書き直しました。 React開発者として、私はこのFiberがどんな種類のもので、どのファイバーがどのタスクを解決するのか、そして最終的にはLive Typingで取り組んでいるプロジェクトで得た知識をどのように、そしてどのように適用できるのかを理解することにしました。 理解し、さまざまな結論に至りました。







スタックとファイバー



新しいアーキテクチャで何が変更されたかを理解するには、古いアーキテクチャの欠点を理解する必要があります。 たとえば、次のデモを検討してください。









2つのコンポーネントがあり、ソースコードはここで見ることができます最初のコンポーネントはStackと呼ばれる古いバージョンのアーキテクチャで実行され、2番目はFiberを使用しています。 違いは肉眼で認識できます。2番目のコンポーネントのアニメーションは、最初のコンポーネントのアニメーションよりもはるかにスムーズに動作します。







Stackに実装されたコンポーネントのアニメーションの遅延の原因は何ですか? ブラウザで[パフォーマンス]タブを開いて、[フレーム]フィールドと、SierpinskiTriangle関数のランタイム(SierpinskiTriangleコンポーネントのrenderメソッドを意味する)を見てみましょう。 この機能では、古い仮想ツリーと新しい仮想ツリーを比較するプロセスが実行されます。 フレーム変更の速度は、このプロセスの実行速度に依存します。 この場合、700ミリ秒に相当し、これは長い時間です。







スタック

図1. Stackコアでのコンポーネント操作







ここから、古いアーキテクチャの主な問題は、SierpinskiTriangleコンポーネントのrenderメソッドの長時間の実行であると結論付けることができます。 アルゴリズム自体の最適化により、加速することはほとんど不可能でした。







図2は、React on a Fiberコアがコンポーネントをどのようにレンダリングするかを示しています。 フレームは17ミリ秒ごとに1回の頻度で変化することがわかります。 大まかに言って、Fiberは何らかの方法で、長い時間を要する関数を高速の小さな関数に分割します。







繊維

図2.ファイバーコアでのコンポーネント操作







理論上の繊維



ファイバーはどのように機能をパーツに分割しますか? これを行うには、この機能を実行するプロセスを管理するとともに、次の機能を提供する必要があります。









上記を実装するには、古いDOMツリーと新しいDOMツリーを比較して部分ごとに作業を分割する方法を決定する必要があります。







Reactを見ると、Reactのすべてのコンポーネントは関数です。 また、Reactアプリケーションのレンダリングは、最も若いコンポーネントから最も古いコンポーネントへの再帰的な関数呼び出しです。 コンポーネントを変更する機能が長時間機能する場合、遅延が発生することは既に確認しました。 この問題を解決するために、ブラウザーを提供する2つの方法を使用できます。







  1. requestIdleCallback。これにより、メインのブラウザスレッドがアイドル状態のときに低優先度の計算を実行できます。
  2. requestAnimationFrame。次のフレームでアニメーションの実行をリクエストできます。


最後に、計画は次のとおりです。requestIdleCallbackイベントのインターフェイスの変更の一部を計算する必要があり、コンポーネントを描画する準備ができたらすぐに、requestAnimationFrameを要求します。 ただし、仮想ツリーを比較する機能の実行を何らかの形で中断し、同時に中間結果を維持する必要があります。 この問題を解決するために、React開発者は独自のバージョンの呼び出しスタックを開発することにしました。 その後、関数の実行を停止したり、それをさらに必要とする関数の実行を個別に優先したりすることができます。







Reactコンポーネントのフレームワーク内での呼び出しスタックの再アクティブ化は、新しいファイバーアルゴリズムです。 呼び出しスタックを再実装する利点は、呼び出しスタックをメモリに格納し、停止し、必要なときに起動できることです。







ファイバーの実際:フィボナッチ数を見つける



標準検索の実装



標準呼び出しスタックを使用したフィボナッチ検索の実装を以下に示します。







function fib(n) { if(n <= 2) { return 1; } else { var a = fib(n - 1); var b = fib(n - 2); return a + b; } }
      
      





まず、通常のコールスタックでフィボナッチ数検索機能がどのように実行されるかを見てみましょう。 例として、3番目の数字を探します。







そのため、スタックフレームが呼び出しスタックに作成され、そこにローカル変数と関数引数が格納されます。 この場合、スタックフレームは最初は次のようになります。













なぜなら n> 2の場合、次の行に進みます。







 function fib(n) { if(n <= 2) { return 1; } else { var a = fib(n - 1); //    var b = fib(n - 2); return a + b; } }
      
      





ここで、fib関数が再び呼び出されます。 新しいスタックフレームが作成されますが、nは既に1つ少ない、つまり2です。ローカル変数は未定義のままです。













そして以来 n = 2の場合、関数は1を返し、5行目に戻ります







 function fib(n) { if(n <= 2) { return 1; } else { var a = fib(n - 1); //    var b = fib(n - 2); return a + b; } }
      
      





呼び出しスタックは次のようになります。













次に、変数bのフィボナッチ数検索関数が6行目で呼び出されます。新しいスタックフレームが作成されます。







 function fib(n) { if(n <= 2) { return 1; } else { var a = fib(n - 1); var b = fib(n - 2); //    return a + b; } }
      
      





前の場合と同様に、関数は1を返します。







スタックフレームは次のようになります。













その後、関数はaとbの合計を返します。







Fiberでの検索の実装



免責事項:この場合、呼び出しスタックの再実装でフィボナッチ数の検索がどのように実行されるかを示しました。 同様の方法がFiberによって実装されています。







 function fiberFibonacci(n) { var fiber = { arg: n, returnAddr: null, a: 0 /* b is tail call */ }; rec: while (true) { if (fiber.arg <= 2) { var sum = 1; while (fiber.returnAddr) { fiber = fiber.returnAddr; if (fiber.a === 0) { fiber.a = sum; fiber = { arg: fiber.arg - 2, returnAddr: fiber, a: 0 }; continue rec; } sum += fiber.a; } return sum; } else { fiber = { arg: fiber.arg - 1, returnAddr: fiber, a: 0 }; } } }
      
      





最初に、可変ファイバーを作成します。このファイバーは、スタックフレームです。 argは関数の引数、returnAddrは戻りアドレス、aは関数の値です。







なぜなら この場合のfiber.argは3で、2を超えています。その後、17行目に進みます







 function fiberFibonacci(n) { var fiber = { arg: n, returnAddr: null, a: 0 /* b is tail call */ }; rec: while (true) { if (fiber.arg <= 2) { var sum = 1; while (fiber.returnAddr) { fiber = fiber.returnAddr; if (fiber.a === 0) { fiber.a = sum; fiber = { arg: fiber.arg - 2, returnAddr: fiber, a: 0 }; continue rec; } sum += fiber.a; } return sum; } else { fiber = { arg: fiber.arg - 1, returnAddr: fiber, a: 0 }; //  17 } } }
      
      





新しいファイバー(スタックフレーム)を作成します。 その中に、スタックの前のフレームへのリンクを保存します。引数は1つ少なく、結果の初期値です。 したがって、通常のフィボナッチ数検索関数を再帰的に呼び出すときに作成した呼び出しスタックを再作成します。







次に、反対方向にスタックを反復処理し、フィボナッチ数をカウントします。 7〜15行目。







 var sum = 1; while (fiber.returnAddr) { fiber = fiber.returnAddr; if (fiber.a === 0) { fiber.a = sum; fiber = { arg: fiber.arg - 2, returnAddr: fiber, a: 0 }; continue rec; } sum += fiber.a; } return sum;
      
      





おわりに



Fiberを実装した後、Reactは速くなりますか? このテストによると、いいえ。 さらに約1.5倍遅くなりました。 しかし、新しいアーキテクチャの導入により、ブラウザのメインストリームをより合理的に使用できるようになりました。これにより、アニメーションの作業がよりスムーズになりました。








All Articles