>  기사  >  웹 프론트엔드  >  EventLoop이란 무엇입니까? Node 또는 페이지의 성능을 테스트하는 방법

EventLoop이란 무엇입니까? Node 또는 페이지의 성능을 테스트하는 방법

青灯夜游
青灯夜游앞으로
2023-03-03 19:47:492157검색

모든 사람이 이벤트 루프 메커니즘을 알아야 합니다. 이 기사에서는 EventLoop를 사용하여 노드 또는 페이지 성능을 감지하는 흥미로운 코드를 작성합니다. 그런데 EventLoop이 모든 사람에게 도움이 되기를 바랍니다.

EventLoop이란 무엇입니까? Node 또는 페이지의 성능을 테스트하는 방법

Event Loop

모든 사람은 Event Loop의 메커니즘을 알아야 합니다. 먼저 요약을 반복하겠습니다.

Node.js는 Javascript의 이벤트 루프와 다릅니다. 직관적으로 setImmediateprocess.nextTick라는 두 가지 API가 더 있습니다. 둘째, 런타임이 다르기 때문에 Html Standrad는 여러 페이지 및 DOM 작업과 같은 다양한 소스에 대해 다양한 작업 대기열을 고려합니다. 그리고 Node.js 이벤트 루프 고려할 사항이 그리 많지 않습니다. setImmediateprocess.nextTick 两个 API。其次是由于运行时不一样,Html Standrad 里面会考虑多页面、DOM操作等不同来源会有不同的 task queue 。而 Node.js Event Loop 中需要考虑的没这么多。

按照我的理解,双方在概念上是一致的,可以如此概括(或者看这里):

  • task queue 任务队列。一些事件等会被定义为任务,很多时候会被称为 MacroTask(宏任务)与 MicroTask 进行对应。每次会获取队头的 task 进行执行。

  • microtask queue 微任务队列。会有一个微任务队列,一个 Task 内一般会执行清空微任务队列。

  • 如此往复。

性能测量

在上面的了解之后,有一个简单的对性能进行测量的方法:每秒内完成了多少次 Event Loop 循环,或者说执行了多少个 MacroTask,这样我们大致就能知道代码中同步的代码的执行情况。

测试函数

class MacroTaskChecker {
    constructor(macroTaskDispatcher, count = 1000, cb = () => { }) {
        this.macroTaskDispatcher = macroTaskDispatcher
        this.COUNT = count
        this.cb = cb
    }
    start(cb) {
        this.cb = cb || this.cb
        this.stop = false
        const scope = () => {
            let count = this.COUNT
            const startTime = performance.now()
            const fn = () => {
                count--
                if (count > 0) this.macroTaskDispatcher(fn)
                else {
                    const endTime = performance.now()
                    // 执行 COUNT 次宏任务之后 计算平均每秒执行了多少个
                    this.cb({
                        avg: this.COUNT / (endTime - startTime) * 1000,
                        timestamp: endTime
                    })
                    !this.stop && this.macroTaskDispatcher(scope)
                }
            }
            this.macroTaskDispatcher(fn)
        }
        scope()
    }

    stop() {
        this.stop = true
    }
}

之后,执行一些死循环去测试是否能检测到密集同步代码执行。

function meaninglessRun(time) {
    console.time('meaninglessRun')
    for (let i = time; i--; i > 0) {
        // do nothing
    }
    console.timeEnd('meaninglessRun')
}

setTimeout(() => {
    meaninglessRun(1000 * 1000 * 1000)
}, 1000 * 5)

setTimeout(() => {
    checker.stop()
    console.log('stop')
}, 1000 * 20)

<span style="font-size: 18px;">setTimeout</span>

const checker = new MacroTaskChecker(setTimeout, 100)

checker.start(v => console.log(`time: ${v.timestamp.toFixed(2)} avg: ${v.avg.toFixed(2)}`))

从输出中能明显看到同步阻塞的时候avg是下降的。不过在 browser 和 node.js 上测试两边会有明显差距。【相关教程推荐:nodejs视频教程

// node.js
time: 4837.47 avg: 825.14
time: 4958.18 avg: 829.83
meaninglessRun: 918.626ms
time: 6001.69 avg: 95.95
time: 6125.72 avg: 817.18
time: 6285.07 avg: 635.16
// browser
time: 153529.90 avg: 205.21
time: 154023.40 avg: 204.46
meaninglessRun: 924.463ms
time: 155424.00 avg: 71.62
time: 155908.80 avg: 208.29
time: 156383.70 avg: 213.04

虽然达成我们的目的,但是使用 setTimeout 是不完全能准确记录下每一个任务的。根据 HTML StandradMDN 的说法,setTimeout 最少的会等待4ms。从这个角度看 browser avg * 4ms approx 1000ms。而 node.js 应该是没有遵循 browser 那边的约定,但是也没有执行到记录每一个loop。

<span style="font-size: 18px;">setImmediate</span>

如果使用 node.js 的 setImmediate

const checker = new MacroTaskChecker(setImmediate, 1000 * 10)

可以看到执行次数大概高出  Node.js setTimeout 一个量级:

time: 4839.71 avg: 59271.54
time: 5032.99 avg: 51778.84
meaninglessRun: 922.182ms
time: 6122.44 avg: 9179.95
time: 6338.32 avg: 46351.38
time: 6536.66 avg: 50459.77

按照 Node.js 文档中的解释setImmediate 会在每一个 loop (phase) 的 check 阶段执行。使用 setImmediate 应该是能准确记录每一次 Loop 的。我这台机器大概是 40000 到 60000 之间的循环次数。

<span style="font-size: 18px;">window.postMessage</span>

在 browser 上由于没有 setImmediate 我们可以按照 MDN 上的指引使用 window.postMessage

제가 이해한 바에 따르면 두 당사자는 개념적으로 일관되며 다음과 같이 요약할 수 있습니다(또는 여기): 🎜
  • 🎜작업 대기열 작업 대기열. 일부 이벤트는 태스크로 정의되며, 마이크로태스크(MicroTask)에 대응하기 위해 매크로태스크(Macro Task)라고도 부른다. 매번 대기열의 선두에 있는 작업이 실행을 위해 획득됩니다. 🎜
  • 🎜마이크로태스크 큐 마이크로태스크 큐. 마이크로태스크 대기열이 있으며 일반적으로 마이크로태스크 대기열은 작업 내에서 지워집니다. 🎜
  • 🎜 등등. 🎜
🎜🎜성능 측정🎜🎜🎜위 내용을 이해한 후 성능을 측정하는 간단한 방법이 있습니다. 초당 얼마나 많은 이벤트 루프 사이클이 완료되는지, 또는 매크로 태스크가 몇 번 실행되는지 등입니다. 코드에서 동기화된 코드의 실행을 대략적으로 알 수 있습니다. 🎜🎜🎜테스트 기능🎜🎜
const fns = []
window.addEventListener("message", () => {
    const currentFns = [...fns]
    fns.length = 0
    currentFns.forEach(fn => fn())
}, true);
function messageChannelMacroTaskDispatcher(fn) {
    fns.push(fn)
    window.postMessage(1)
}
🎜그런 다음 무한 루프를 실행하여 집중적인 동기 코드 실행이 감지될 수 있는지 테스트하세요. 🎜
time: 78769.70 avg: 51759.83
time: 78975.60 avg: 48614.49
meaninglessRun: 921.143 ms
time: 80111.50 avg: 8805.14
time: 80327.00 avg: 46425.26
time: 80539.10 avg: 47169.81
🎜🎜<span style="font-size: 18px;">setTimeout</span>🎜🎜
const { port1, port2 } = new MessageChannel();
const fns = []
port1.onmessage = () => {
    const currentFns = [...fns]
    fns.length = 0
    currentFns.forEach(fn => fn())
};
function messageChannelMacroTaskDispatcher(fn) {
    fns.push(fn)
    port2.postMessage(1)
}
🎜동기화가 차단되면 평균이 감소하는 것을 출력에서 ​​명확하게 볼 수 있습니다. 그러나 브라우저와 node.js에서의 테스트에는 분명한 차이가 있습니다. [권장 관련 튜토리얼: nodejs 동영상 튜토리얼]🎜
time: 54974.80 avg: 68823.12
time: 55121.00 avg: 68493.15
meaninglessRun: 925.160888671875 ms
time: 56204.60 avg: 9229.35
time: 56353.00 avg: 67430.88
time: 56503.10 avg: 66666.67
// 一起执行 wp=window.postMessage mc=MessageChannel
wp time: 43307.90 avg: 25169.90
mc time: 43678.40 avg: 27005.13
wp time: 43678.60 avg: 26990.55
mc time: 44065.80 avg: 25833.12
wp time: 44066.00 avg: 25819.78
mc time: 44458.40 avg: 25484.20
🎜우리는 목표를 달성했지만 goal을 사용하지만 setTimeout을 사용하면 모든 작업을 정확하게 기록할 수 없습니다. HTML StandradMDN에 따르면 setTimeout은 최소 4ms를 기다립니다. 이 관점에서 볼 때 브라우저 평균 * 4ms ©대략 수학> 1000ms. Node.js는 브라우저의 규칙을 따르지 않을 수도 있지만 모든 루프를 기록하지는 않습니다. 🎜🎜🎜<span style="font-size: 18px;">setImmediate</span>🎜🎜🎜node.js의 setImmediate를 사용하는 경우: 🎜
mc time: 460.99 avg: 353930.80
mc time: 489.52 avg: 355088.11
mc time: 520.30 avg: 326384.64
mc time: 551.78 avg: 320427.29
🎜예 실행 횟수가 Node.js setTimeout보다 약 10배 높은 것을 볼 수 있습니다. 🎜
...
(messagechannel) time: 1231.10 avg: 355569.31
(messagechannel) time: 1260.14 avg: 345825.77
(setImmediate) time: 1269.95 avg: 339.27
(setTimeout) time: 1270.09 avg: 339.13
(messagechannel) time: 1293.80 avg: 298141.74
(messagechannel) time: 1322.50 avg: 349939.04
...
🎜 설명, setImmediate는 각 루프(단계)의 검사 단계에서 실행됩니다. setImmediate를 사용하면 모든 루프를 정확하게 기록할 수 있어야 합니다. 내 기계의 사이클 수는 40,000에서 60,000 사이입니다. 🎜🎜🎜<span style="font-size: 18px;">window.postMessage</span>🎜🎜🎜브라우저에 setImmediate가 없으므로 우리는 MDN을 따를 수 있습니다. 위의 지침은 window.postMessage를 사용하여 구현합니다. 🎜

如果想在浏览器中实现 0ms 延时的定时器,你可以参考这里所说的 window.postMessage()

const fns = []
window.addEventListener("message", () => {
    const currentFns = [...fns]
    fns.length = 0
    currentFns.forEach(fn => fn())
}, true);
function messageChannelMacroTaskDispatcher(fn) {
    fns.push(fn)
    window.postMessage(1)
}

可以看到和 node.js setImmediate 量级是一致的。

time: 78769.70 avg: 51759.83
time: 78975.60 avg: 48614.49
meaninglessRun: 921.143 ms
time: 80111.50 avg: 8805.14
time: 80327.00 avg: 46425.26
time: 80539.10 avg: 47169.81

<span style="font-size: 18px;">MessageChannel</span>

browser

理论上 browser 使用 MessageChannel 应该也是可以的,还避免了无效的消息被其他 window.addEventListener("message", handler) 接收:

const { port1, port2 } = new MessageChannel();
const fns = []
port1.onmessage = () => {
    const currentFns = [...fns]
    fns.length = 0
    currentFns.forEach(fn => fn())
};
function messageChannelMacroTaskDispatcher(fn) {
    fns.push(fn)
    port2.postMessage(1)
}

不是很懂为啥会比 window.postMessage 频繁一点,同时启动两个 checker 的话可以看到 log 是成对出现的,也就是说一个loop内大家都只执行了一次。我猜测是 window.postMessage 的实现方式消耗会大一些。

time: 54974.80 avg: 68823.12
time: 55121.00 avg: 68493.15
meaninglessRun: 925.160888671875 ms
time: 56204.60 avg: 9229.35
time: 56353.00 avg: 67430.88
time: 56503.10 avg: 66666.67
// 一起执行 wp=window.postMessage mc=MessageChannel
wp time: 43307.90 avg: 25169.90
mc time: 43678.40 avg: 27005.13
wp time: 43678.60 avg: 26990.55
mc time: 44065.80 avg: 25833.12
wp time: 44066.00 avg: 25819.78
mc time: 44458.40 avg: 25484.20

node

在 node.js 上也有 MessageChannel ,是否也可以用来测量loop次数呢?

mc time: 460.99 avg: 353930.80
mc time: 489.52 avg: 355088.11
mc time: 520.30 avg: 326384.64
mc time: 551.78 avg: 320427.29

量级很不正常。理论上不应该超过 setImmediate 的。如果同时启动 setImmediatesetTimeout 的 checker:

...
(messagechannel) time: 1231.10 avg: 355569.31
(messagechannel) time: 1260.14 avg: 345825.77
(setImmediate) time: 1269.95 avg: 339.27
(setTimeout) time: 1270.09 avg: 339.13
(messagechannel) time: 1293.80 avg: 298141.74
(messagechannel) time: 1322.50 avg: 349939.04
...

很明显跟不是宏任务了。我猜测 MessageChannel 在 node.js 被归入到跟 socket 等同级别了,就是超出阈值之后的任务会移动到下一个loop中。

总结

使用这种方式去检测性能还挺有趣的,正式使用的话这个指标感觉过于不稳定(即使什么都没做都会有20%-30%的振动)。推荐和其他正经的办法(比如 performance 等)结合。

同时这种方式非常有可能影响正常的 Event Loop,比如 Node.js 中会有一个 pull 的阶段,在执行完全部微任务后,没有任何 timer 的话是会停留在这个阶段,准备马上执行下一个出现的微任务。

顺便复习了下 Event Loop。没想到的是 MessageChannel 在两边的差距居然有这么大。

更多node相关知识,请访问:nodejs 教程

위 내용은 EventLoop이란 무엇입니까? Node 또는 페이지의 성능을 테스트하는 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 juejin.cn에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제