mquickjs vs Node.js 对比

近期 quickjs 的作者发布了 mquickjs,在保持性能接近的情况下,内存占用大幅缩小,运行时整体内存占用只需要 100kb。

由于好奇 mquickjs 在内存和执行速度上到底怎样,我跑了一些跟 Node.js 和 QuickJS 的对比。

Node.js 作为目前最常用的运行时,在大而全的同时也有着一些自己的问题,比如启动时间长,资源占用多,V8 的 JIT 特性使得代码冷启动性能不好等等。这也使得社区出现了例如 bun、deno 等等多个运行时。严格来说 mquickjs 不是一个完整的 runtime,而是一个 engine,对标的应该是 V8。这里我们为了方便就直接跟 Node.js 来做对比了。

同时,与 QuickJS 的对比也可以看一下 mquickjs 有哪些特殊之处。

mquickjs 的使用场景主要在资源有限的嵌入式领域,或者终端设备的脚本执行等。由于自己做 SSR 的经历,本次更想要探索的是 Node.js 中这么一个场景:

  • 有茫茫多的组件需要渲染:这意味着有非常多的 JS 文件,流量分布不均匀,存在大量的长尾流量。这些长尾流量在首次执行时性能不能太差,否则会导致渲染超时。
  • 需要进行并行的渲染:使用线程或者进程并行渲染时,资源的占用情况会被放大,例如一个实例启动 100 个进程,内存占用就是 100 倍。

使用 Node.js 在这个场景中遇到了很多问题,那么 mquickjs 可以解决这个问题吗?之前我们尝试过 quickjs,这次看看 mquickjs 是否会有不同。

启动时间

  • 测试文件:简单的 console.log('Hello, World!')
  • 测量工具:Unix time 命令

结果:

运行时 启动时间
Node.js 158ms
mquickjs 2ms
QuickJS 3ms

可以看到 mquickjs 和 QuickJS 的启动速度几乎可以用”瞬时”来形容。可能非常适合临时拉起一个实例来做渲染,之后直接废弃。

当然,引擎的启动时间只是整个实例启动的一(小)部分,Node.js 中加载了很多 lib 库的逻辑,实际使用场景中还需要加载业务逻辑代码,这些都会占用很多时间。

SSR模拟测试(纯JS执行时间)

场景:模拟服务端渲染,1000次字符串拼接。

运行时 执行时间
Node.js 0.42ms
mquickjs 9ms
QuickJS 0.47ms

我们可以看到 Node.js (V8) 和 QuickJS 在纯 JS 计算性能上要远远优于 mquickjs。

内存使用

场景 Node.js mquickjs QuickJS
简单脚本 46,184 KB 1,664 KB 2,688 KB
SSR模拟 46,700 KB 10,880 KB 2,816 KB
加载1MB文件 48,052 KB 5,452 KB 3,960 KB

可以看到 mquickjs 的内存占用确实非常少,仅为 Node.js 的 4-23%,这使得我们可以在实例中轻松部署非常多的 mquickjs 实例。一百个实例,光运行时的内存占用就是 4.5G -> 160M 的差距。

但我们也可以发现,JavaScript 代码的体积是不可避免的,如果代码量非常大(我之前的业务场景),优化代码的体积也是有价值的。另外也要注意 Node.js 里除了 V8 还有很多其他 lib 代码,这些代码也是要占体积的,而如果我们想要 mquickjs 跑起来,必然也要补充这些 lib 代码,内存差异会被一定程度抹平。

最后,虽然 mquickjs 初始化内存占用低,但加载逻辑代码后,内存占用会显著上升,高于 QuickJS,可能跟 mquickjs 的垃圾回收等内存管理机制有关系。

JIT 效果

V8 很快,很重要的一个原因就是可以在执行多次后进行优化,直接使用 native 代码来跑。我运行了 10 次 Mandelbrot 分形计算,观察编译器的优化效果。

Node.js(有JIT优化)

迭代次数 执行时间 性能变化
第0次 217ms 冷启动(基线)
第1-9次(均) ~28ms 性能提升约 87%

首次运行较慢(217ms),后续运行稳定在 28ms 左右,性能提升了 **87%**。这是JIT编译器在运行时优化字节码的典型表现。

mquickjs(无JIT,解释执行)

迭代次数 mquickjs 执行时间 (均) QuickJS 执行时间 (均) 性能变化
第0-9次 ~79ms ~67ms 无变化

所有迭代执行时间一致,无任何优化。解释执行的性能稳定但较慢。

可以看到:

  • mquickjs 和 QuickJS 性能一致性强,可预测性好,但性能绝对值与优化后的 V8 差距巨大。
  • mquickjs 确实做到了与 QuickJS 接近的性能(单这个场景)。

微基准测试综合对比

mquickjs 的代码中有一个 microbench 测试用例,简单的修改就可以在 Node.js 和 QuickJS 中跑起来,结果如下_(总分根据各项测试的“纳秒/操作”计算得出)_:

指标 (越低越好) Node.js (V8) mquickjs QuickJS
总分 (相对性能) 0.13x 0.99x 1.0x

Node.js (V8) 在这个微基准测试中展现了压倒性的优势,其性能大约是 QuickJS 的 7-8倍,是 mquickjs 的 7.6倍。mquickjs 和 QuickJS 的性能则非常接近。

总结

可以看到 mquickjs 的内存占用确实非常小,但性能上跟 V8 差距还是很大的。

另外这些都是一些局部的测试,具体的选型还是需要看实际的使用场景。

对比过程中使用的脚本可以在 GitHub仓库中找到:javascript_runtimes