📜 ⬆️ ⬇️

Slower, Smoother: Understanding React Fiber


September 16, 2017 React Fiber was released - a new major version of the library. In addition to adding new features that you can read about here , the developers rewrote the core architecture of the library. As a React-developer, I decided to figure out what kind of beast this Fiber is, what tasks it solves, due to what and how you can finally apply this knowledge on the projects I work for at Live Typing . Understood and came to ambiguous conclusions.


Stack vs fiber


To understand what has changed in the new architecture, you need to understand the flaws of the old. For example, consider the following demo:



We have two components, the source code of which you can see here. The first component works on the old version of the architecture, which was called Stack, the second - with the help of Fiber. The difference is noticeable to the naked eye: the animation of the second component works much smoother than the animation of the first.


What causes the delay in the animation of the component implemented on the Stack? Let's open the Performance tab in the browser and look at the Frames field, as well as at the time the SierpinskiTriangle function is executed (by this we mean the execution of the render method of the SierpinskiTriangle component). In this function, the process of comparing the old and the new virtual tree takes place. From how quickly this process is performed, the frequency of the frame change depends. In this case, it is 700 ms, and that is a long time.


Stack
Figure 1. Component running on the Stack core


From here we can conclude that the main problem of the old architecture was the long execution of the render method of the SierpinskiTriangle component. It would hardly have been possible to speed it up due to some optimization of the algorithm itself.


Figure 2 illustrates how React on the Fiber core renders a component. We see that frames change with frequency once every 17 ms. Roughly speaking, Fiber somehow breaks down a function that runs for a long time into small functions that are performed quickly.


Fiber
Figure 2. Component running on the Fiber core


Fiber in theory


How does Fiber break up a function? To do this, you must manage the process of performing this function, as well as provide the opportunity to:



To implement the above, we need to determine how to divide the work of comparing the old and the new DOM trees into parts.


If you look at React, then all components in it are functions. And drawing a React application is a recursive function call from the youngest component to the most senior component. We have already seen that if the function of changing our component takes a long time to work out, then there is a delay. To solve this problem, we can use two methods that provide browsers:


  1. requestIdleCallback, which allows you to perform calculations with low priority while the main browser thread is idle;
  2. requestAnimationFrame, which allows you to make a request to execute our animation in the next frame.

As a result, the plan is this: we need to calculate the part of the change of our interface by the requestIdleCallback event, and, as soon as we are ready to render the component, request the requestAnimationFrame in which this will happen. But it is still necessary to somehow interrupt the execution of the function of comparing virtual trees and at the same time preserve intermediate results. To solve this problem, the React developers decided to develop their version of the call stack. Then they will have the opportunity to stop the execution of functions, independently give priority to the performance of functions that need it more, and so on.


Reimplementation of the call stack within React components is the new Fiber algorithm. The advantage of reimplementing the call stack is that you can store it in memory, stop and start it when you need it.


Fiber in practice: finding the Fibonacci number


Standard search implementation


The implementation of the Fibonacci number search using the standard call stack can be seen below.


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

First, let's analyze how the Fibonacci number search function is performed on a normal call stack. As an example, we will look for the third number.


So, a stack frame is created in the call stack, in which local variables and function arguments will be stored. In this case, the stack frame will initially look like this:



Since n> 2, then we get to the next line:


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

Here the function fib will be called again. A new stack frame will be created, but n will already be one less, that is, 2. Local variables will still be undefined.



And since n = 2, then the function returns one, and we go back to line 5


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

The call stack looks like this:



Next, the Fibonacci number search function is called for the variable b, line 6. A new stack frame is created:


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

The function, as in the previous case, returns 1.


The stack frame looks like this:



Then the function returns the sum of a and b.


Implementing Fiber Search


Disclaimer: In this case, we have shown how the search for the Fibonacci number is performed with the reimplementation of the call stack. A similar method is implemented by 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 }; } } } 

Initially, we create a variable fiber, which in our case is a stack frame. arg is the argument of our function, returnAddr is the return address, a is the value of the function.


Since fiber.arg in our case is 3, which is more than 2, then we go to line 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 } } } 

where we create a new fiber (stack frame). In it, we save the reference to the previous stack frame, the argument is one less and the initial value of our result. Thus, we recreate the call stack, which we created when recursively calling the usual function of searching for the Fibonacci number.


Then we iterate backwards on our stack and count our Fibonacci number. lines 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; 

Conclusion


Has React become faster after deploying Fiber? According to this test , no. He became even slower about 1.5 times. But the introduction of the new architecture made it possible to use the main stream of the browser more rationally, due to which the work of animations became smoother.


')

Source: https://habr.com/ru/post/343504/


All Articles