Heim  >  Artikel  >  Web-Frontend  >  Was ist EventLoop? So testen Sie die Leistung eines Knotens oder einer Seite

Was ist EventLoop? So testen Sie die Leistung eines Knotens oder einer Seite

青灯夜游
青灯夜游nach vorne
2023-03-03 19:47:492089Durchsuche

Jeder sollte den Event-Loop-Mechanismus kennen. Dieser Artikel verwendet EventLoop, um einen interessanten Code zur Erkennung der Knoten- oder Seitenleistung zu erstellen. Ich hoffe, dass er für alle hilfreich ist.

Was ist EventLoop? So testen Sie die Leistung eines Knotens oder einer Seite

Ereignisschleife

Jeder sollte den Mechanismus der Ereignisschleife kennen. Lassen Sie mich zunächst die Zusammenfassung wiederholen.

Die Ereignisschleife von Node.js unterscheidet sich von der von Javascript. Intuitiv gibt es zwei weitere APIs: setImmediate und process.nextTick. Zweitens, weil die Laufzeit unterschiedlich ist, Html Standard berücksichtigt unterschiedliche Aufgabenwarteschlangen für unterschiedliche Quellen, z. B. mehrere Seiten und DOM-Vorgänge. Und Node.js Event Loop Es gibt nicht so viel zu beachten. 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

Nach meinem Verständnis sind die beiden Parteien konzeptionell konsistent und können wie folgt zusammengefasst werden (oder siehe hier): 🎜
  • 🎜Aufgabenwarteschlange Aufgabenwarteschlange. Einige Ereignisse werden als Aufgaben definiert und häufig als MacroTask (Makroaufgabe) bezeichnet, um MicroTask zu entsprechen. Jedes Mal wird die Aufgabe an der Spitze der Warteschlange zur Ausführung abgerufen. 🎜
  • 🎜Mikrotask-Warteschlange Mikrotask-Warteschlange. Es gibt eine Mikrotask-Warteschlange, und die Mikrotask-Warteschlange wird normalerweise innerhalb einer Aufgabe gelöscht. 🎜
  • 🎜Und so weiter. 🎜
🎜🎜Leistungsmessung🎜🎜🎜Nachdem wir das oben Gesagte verstanden haben, gibt es eine einfache Möglichkeit, die Leistung zu messen: wie viele Ereignisschleifenzyklen pro Sekunde abgeschlossen werden oder wie viele MacroTasks ausgeführt werden, damit wir kann die Ausführung des synchronisierten Codes im Code grob kennen. 🎜🎜🎜Testfunktion🎜🎜
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)
}
🎜Führen Sie danach einige Endlosschleifen aus, um zu testen, ob eine intensive synchrone Codeausführung erkannt werden kann. 🎜
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)
}
🎜Aus der Ausgabe ist deutlich ersichtlich, dass der Durchschnitt abnimmt, wenn die Synchronisierung blockiert ist. Es wird jedoch offensichtliche Unterschiede zwischen den beiden Tests für Browser und node.js geben. [Empfohlene verwandte Tutorials: nodejs-Video-Tutorial]🎜
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
🎜Obwohl wir unser Ziel erreicht haben goal , aber die Verwendung von setTimeout ist nicht vollständig in der Lage, jede Aufgabe genau aufzuzeichnen. Laut HTML-Standard und MDN wartet setTimeout mindestens 4 ms. Aus dieser Perspektive betrachtet Browser-Durchschnitt * 4 ms ungefähr math> 1000ms. Node.js folgt wahrscheinlich nicht den Konventionen des Browsers, zeichnet jedoch nicht jede Schleife auf. 🎜🎜🎜<span style="font-size: 18px;">setImmediate</span>🎜🎜🎜Wenn Sie setImmediate von node.js verwenden: 🎜
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
🎜Ja Es ist ersichtlich, dass die Anzahl der Ausführungen etwa eine Größenordnung höher ist als die von Node.js setTimeout: 🎜
...
(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
...
🎜Laut Erklärung, setImmediate wird in der Prüfphase jeder Schleife (Phase) ausgeführt. Mit setImmediate sollte jede Schleife genau aufgezeichnet werden können. Meine Maschine hat eine Zyklenzahl zwischen 40.000 und 60.000. 🎜🎜🎜<span style="font-size: 18px;">window.postMessage</span>🎜🎜🎜Da es im Browser kein setImmediate gibt, haben wir kann MDN folgen. Die obigen Anweisungen verwenden window.postMessage, um eines zu implementieren. 🎜

如果想在浏览器中实现 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 教程

Das obige ist der detaillierte Inhalt vonWas ist EventLoop? So testen Sie die Leistung eines Knotens oder einer Seite. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:juejin.cn. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen