preface

That was one of the questions gay friends were asked when they were interviewing RingCenter

On the surface, it looks at the basics like probability theory, but in reality it might also ask about the basics like event loops, and React Fiber

Monte Carlo method for calculating PI

Monte Carlo might be a little confusing, but let’s call it random sampling

A unit square and a quarter unit circle are constructed, and points are put into the unit square. According to the distance between the points and the origin, whether the points fall inside or outside the circle is judged. The number of points falling in the two regions is counted respectively, n1,n2, and N1 /(n1+n2), which is the estimated value of the area of the quarter circle, and π is obtained

Here is the JS code

function inCicle() {
  var x = Math.random();
  var y = Math.random();
  return Math.pow(x, 2) + Math.pow(y, 2) < 1
}
function calcPi() {
  const N = 1e+6
  let pointsInside = 0
  for(let i=0; i<N; i++){if(inCicle()){ pointsInside++; }}return 4 * pointsInside / N
}
calcPi()
Copy the code

Running directly on the console, it will be found that there is a lag and frame drop, let’s talk about how to solve it

How do I avoid blocking the main thread

CalcPi is a time-consuming task that blocks the main thread and even causes frames to drop.

Here are a few ideas

  1. Web Worker
  2. requestIdleCallback
  3. requestAnimationFrame + MessageChannel

Web Worker

What Web Worker is no longer introduced, do not understand their own MDN search

We create a new Worker thread to compute the time-consuming task and then send the results to the main thread

function createWorker () {
  let text = ` function inCicle() { var x = Math.random(); var y = Math.random(); return Math.pow(x, 2) + Math.pow(y, 2) < 1 } function calcPi() { const N = 1e+6 let pointsInside = 0 for(let i=0; i
      
        { let pi = calcPi() this.postMessage(pi); }, false); `
      ;>
  let blob = new Blob([text]);
  let url = window.URL.createObjectURL(blob);
  return new Worker(url)
}

let worker = createWorker()
worker.onmessage = (evt) = > {
  console.log('PI: ', evt.data)
};
worker.postMessage("calc");
Copy the code

The disadvantage is that the number of calculations is fixed, and the results of real-time calculations cannot be seen

requestIdleCallback

Take advantage of the feature of requestIdleCallback that performs tasks in the spare time of frames to calculate time-consuming tasks

<! DOCTYPE html><html>

<head>
  <title>Scheduling background tasks using requestIdleCallback</title>
</head>

<body>
  <script>
    var requestId = 0;
    var pointsTotal = 0;
    var pointsInside = 0;

    function piStep() {
      var r = 1;
      var x = Math.random() * r;
      var y = Math.random() * r;
      return (Math.pow(x, 2) + Math.pow(y, 2) < Math.pow(r, 2))}function refinePi(deadline) {
      while (deadline.timeRemaining() > 0) {
        if (piStep())
          pointsInside++;
        pointsTotal++;
      }
      currentEstimate = (4 * pointsInside / pointsTotal);
      textElement = document.getElementById("piEstimate");
      textElement.innerHTML = "Pi Estimate: " + currentEstimate;
      requestId = window.requestIdleCallback(refinePi);
    }
    function start() {
      textElement = document.getElementById("piEstimate");
      textElement.innerHTML = "Pi Estimate: " + "loading";
      requestId = window.requestIdleCallback(refinePi);
    }
    function stop() {
      // alert(1)
      if (requestId)
        window.cancelIdleCallback(requestId);
      requestId = 0;
    }
  </script>

  <button onclick="start()">Click me to start!</button>
  <button onclick="stop()">Click me to stop!</button>
  <div id="piEstimate">Not started</div>
</body>

</html>
Copy the code

A few points

  1. Dom changes made in requestIdleCallback can only be rendered during the next Update Rendering phase

The piEstimate innerHTML frame is rendered inconsistently before and after stop

  1. RequestIdleCallback has compatibility issues. RequestAnimationFrame and MessageChannel are commonly used to fallback

requestAnimationFrame + MessageChannel

RequestAnimationFrame will be executed before the actual rendering of the UI Render phase in the event loop, which can be simply understood as the initial stage of frame rendering

MessageChannel is used to send and receive messages to start a macro task that can be executed faster than setTimeout (4ms reason)

We set a markPoint in requestAnimationFrame and initiate a macro task via MessageChannel that expires at markPoint + Timeout (16ms). After this time, the task is no longer executed

This ensures that macro tasks don’t lag and drop frames because they take too long to execute

<! DOCTYPE html><html>

<head>
  <title>Scheduling background tasks using requestIdleCallback</title>
</head>

<body>
  <script>
    const timeout = 16 // The default frame is 16ms
    var requestId = 0;
    var pointsTotal = 0;
    var pointsInside = 0;
    let currentTask = {
      startTime: 0.endTime: 0,}var channel = new MessageChannel();
    var sender = channel.port2; // port2 is used to send messages
    channel.port1.onmessage = function (event) {
      if (performance.now() > currentTask.endTime) {
        // It is possible that some other macro task was inserted which caused the task to expire
        requestId = requestAnimationFrame(markPoint)
        return
      }
      refinePi(currentTask.endTime)
      requestId = requestAnimationFrame(markPoint)
    }
    function piStep() {
      var r = 1;
      var x = Math.random() * r;
      var y = Math.random() * r;
      return (Math.pow(x, 2) + Math.pow(y, 2) < Math.pow(r, 2))}function refinePi(deadline) {
      while (performance.now() < deadline) {
        if (piStep()) {
          pointsInside++;
        }
        pointsTotal++;
      }
      currentEstimate = (4 * pointsInside / pointsTotal);
      textElement = document.getElementById("piEstimate");
      textElement.innerHTML = "Pi Estimate: " + currentEstimate;
    }
    function markPoint(timestamp) {
      currentTask.startTime = timestamp
      currentTask.endTime = timestamp + timeout
      // Next round of macro tasks
      sender.postMessage("")}function start() {
      requestId = requestAnimationFrame(markPoint)
    }
    function stop() {
      // alert(1)
      if (requestId)
        window.cancelAnimationFrame(requestId);
      requestId = 0;
    }
    function handle() {
      let start = performance.now()
      while (performance.now() - start < 100) {}}</script>

  <button onclick="start()">Click me to start!</button>
  <button onclick="stop()">Click me to stop!</button>
  <button onclick="handle()">Perform time-consuming tasks and observe the calculation of PI</button>
  <div id="piEstimate">Not started</div>
</body>

</html>
Copy the code

The online test

Develop reading

  1. Cooperative Scheduling of Background Tasks
  2. react-scheduler