非常快的无限循环,不会阻塞 I/O

2022-01-10 00:00:00 node.js electron javascript

对于不阻塞 I/O 的无限循环,是否有比 window.requestAnimationFrame() 更快的替代方法?

Is there a faster alternative to window.requestAnimationFrame() for endless loops that don't block I/O?

我在循环中所做的与动画无关,所以我不在乎下一帧何时准备好,并且我读过 window.requestAnimationFrame() 的上限为显示器的刷新率或至少等到可以绘制帧.

What I'm doing in the loop isn't related to animation so I don't care when the next frame is ready, and I have read that window.requestAnimationFrame() is capped by the monitor's refresh rate or at least waits until a frame can be drawn.

我也尝试了以下方法:

function myLoop() {
    // stuff in loop
    setTimeout(myLoop, 4);
}

(4 是因为这是 setTimeout 中的最小间隔,较小的值仍将默认为 4.)但是,我需要比这更好的分辨率.

(The 4 is because that is the minimum interval in setTimeout and smaller values will still default to 4.) However, I need better resolution than this.

还有什么性能更好的吗?

Is there anything with even better performance out there?

我基本上需要 while(true) 的非阻塞版本.

I basically need a non-blocking version of while(true).

推荐答案

两个比那个setTimeout运行得更快的东西:

Two things that will run sooner than that setTimeout:

  • process.nextTick 回调(NodeJS 特定):

  • process.nextTick callbacks (NodeJS-specific):

process.nextTick() 方法将回调添加到下一个滴答队列".一旦事件循环的当前轮次运行完成,当前在下一个滴答队列中的所有回调都会被调用.

The process.nextTick() method adds the callback to the "next tick queue". Once the current turn of the event loop turn runs to completion, all callbacks currently in the next tick queue will be called.

这不是 setTimeout(fn, 0) 的简单别名.它效率更高.它在事件循环的后续滴答中触发任何其他 I/O 事件(包括计时器)之前运行.

This is not a simple alias to setTimeout(fn, 0). It is much more efficient. It runs before any additional I/O events (including timers) fire in subsequent ticks of the event loop.

  • 承诺结算通知

  • Promise settlement notifications

    因此,这些可能是您工具带的工具,可以将其中一个或两个与 setTimeout 混合使用以达到您想要的平衡.

    So those might be a tools for your toolbelt, doing a mix of one or both of those with setTimeout to achieve the balance you want.

    详情:

    您可能知道,给定的 JavaScript 线程基于任务队列(规范称其为作业队列)运行;您可能知道,浏览器中有一个主要的默认 UI 线程,而 NodeJS 运行一个线程.

    As you probably know, a given JavaScript thread runs on the basis of a task queue (the spec calls it a job queue); and as you probably know, there's one main default UI thread in browsers and NodeJS runs a single thread.

    但事实上,现代实现中至少有两个任务队列:我们都认为的主要任务队列(setTimeout 和事件处理程序放置任务的地方),以及微任务"队列,在主任务(或宏任务")的处理过程中放置​​了某些异步操作.一旦宏任务完成,那些微任务就会被处理,在主队列中的下一个宏任务之前 —即使下一个宏任务在微任务之前排队.

    But in fact, there are at least two task queues in modern implementations: The main one we all think of (where setTimeout and event handlers put their tasks), and the "microtask" queue where certain async operations are placed during the processing of a main task (or "macrotask"). Those microtasks are processed as soon as the macrotask completes, before the next macrotask in the main queue — even if that next macrotask was queued before the microtasks were.

    nextTick 回调和承诺结算通知都是微任务.因此,调度任一调度异步回调,但将在下一个主要任务之前发生.

    nextTick callbacks and promise settlement notifications are both microtasks. So scheduling either schedules an async callback, but one which will happen before the next main task.

    我们可以在带有 setInterval 和 promise 解析链的浏览器中看到:

    We can see that in the browser with setInterval and a promise resolution chain:

    let counter = 0;
    
    // setInterval schedules macrotasks
    let timer = setInterval(() => {
      $("#ticker").text(++counter);
    }, 100);
    
    // Interrupt it
    $("#hog").on("click", function() {
      let x = 300000;
    
      // Queue a single microtask at the start
      Promise.resolve().then(() => console.log(Date.now(), "Begin"));
    
      // `next` schedules a 300k microtasks (promise settlement
      // notifications), which jump ahead of the next task in the main
      // task queue; then we add one at the end to say we're done
      next().then(() => console.log(Date.now(), "End"));
    
      function next() {
        if (--x > 0) {
          if (x === 150000) {
            // In the middle; queue one in the middle
            Promise.resolve().then(function() {
              console.log(Date.now(), "Middle");
            });
          }
          return Promise.resolve().then(next);
        } else {
          return 0;
        }
      }
    });
    
    $("#stop").on("click", function() {
      clearInterval(timer);
    });

    <div id="ticker">&nbsp;</div>
    <div><input id="stop" type="button" value="Stop"></div>
    <div><input id="hog" type="button" value="Hog"></div>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

    当您运行它并单击 Hog 按钮时,请注意计数器显示是如何冻结的,然后又会继续显示.这是因为提前安排了 300,000 个微任务.还要注意我们编写的三个日志消息的时间戳(它们不会出现在代码段控制台中,直到宏任务显示它们,但时间戳会显示它们何时被记录).

    When you run that and click the Hog button, note how the counter display freezes, then keeps going again. That's because of the 300,000 microtasks that get scheduled ahead of it. Also note the timestamps on the three log messages we write (they don't appear in the snippet console until a macrotask displays them, but the timestamps show us when they were logged).

    所以基本上,您可以安排一堆微任务,并定期让这些微任务用完并运行下一个宏任务.

    So basically, you could schedule a bunch of microtasks, and periodically let those run out and run the next macrotask.

    注意:我在片段中的浏览器示例中使用了 setInterval,但具体而言,setInterval 可能不是一个好的选择对于使用 NodeJS 进行的类似实验,因为 NodeJS 的 setInterval 与浏览器中的有点不同,并且具有一些令人惊讶的时序特征.

    Note: I've used setInterval for the browser example in the snippet, but setInterval, specifically, may not be a good choice for a similar experiment using NodeJS, as NodeJS's setInterval is a bit different from the one in browsers and has some surprising timing characteristics.

  • 相关文章