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.
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. setImmediate
和 process.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 Standrad 和 MDN 的说法,setTimeout 最少的会等待4ms。从这个角度看 browser avg * 4ms 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
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) }
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
<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
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 ...
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 使用 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.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
的。如果同时启动 setImmediate
和 setTimeout
的 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!