湖滨区建设局网站,哪些网站布局设计做的比较好的,扬中网站建设机构,江苏中禾建设网站Event Loop 机制大家应该都有了解。本文利用 EventLoop 去做一个有趣的检测node或页面性能的代码#xff0c;顺便介绍了一下EventLoop#xff0c;希望对大家有所帮助#xff01; Event Loop
Event Loop 机制大家应该都有了解。我先重复总结一下。
Node.js 和 Javascript 的…Event Loop 机制大家应该都有了解。本文利用 EventLoop 去做一个有趣的检测node或页面性能的代码顺便介绍了一下EventLoop希望对大家有所帮助 Event Loop
Event Loop 机制大家应该都有了解。我先重复总结一下。
Node.js 和 Javascript 的 Event Loop 不太一样直观上是多了 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 macroTaskDispatcherthis.COUNT countthis.cb cb}start(cb) {this.cb cb || this.cbthis.stop falseconst scope () {let count this.COUNTconst 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) setTimeout
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 上测试两边会有明显差距。
// 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
// browse
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。
setImmediate
如果使用 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 之间的循环次数。
window.postMessage
在 browser 上由于没有 setImmediate 我们可以按照 MDN 上的指引使用 window.postMessage 实现一个。
const fns []
window.addEventListener(message, () {const currentFns [...fns]fns.length 0currentFns.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 MessageChannel
browser
理论上 browser 使用 MessageChannel 应该也是可以的还避免了无效的消息被其他 window.addEventListener(message, handler) 接收
const { port1, port2 } new MessageChannel();
const fns []
port1.onmessage () {const currentFns [...fns]fns.length 0currentFns.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
// 一起执行 wpwindow.postMessage mcMessageChannel
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 的。如果同时启动 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 在两边的差距居然有这么大。