文章摘要
V8团队通过优化JSON.stringify函数,使其性能提升了一倍以上。核心改进在于引入了一个无副作用的高速路径,确保在序列化对象时不会触发任何副作用,从而使用更快的专用实现。这一优化显著提升了网页交互速度和应用程序响应性。
文章总结
如何使 JSON.stringify 性能提升两倍以上
JSON.stringify 是 JavaScript 中用于序列化数据的核心函数,其性能直接影响网络请求、数据存储等常见操作。V8 团队近期通过一系列技术优化,使 JSON.stringify 的性能提升了两倍以上。以下是主要优化措施的概述:
无副作用的快速路径
优化的核心在于引入了一个新的快速路径,前提是确保序列化对象时不会触发任何副作用(如执行用户代码或触发垃圾回收)。只要 V8 能确定序列化过程无副作用,就可以使用高度优化的实现,绕过许多昂贵的检查和防御性逻辑,从而显著提升性能。此外,新的快速路径采用迭代而非递归的方式,不仅避免了栈溢出检查,还能处理更深层次嵌套的对象。
处理不同的字符串表示
V8 中的字符串可以以单字节或双字节字符表示。为了避免统一实现中的分支和类型检查,字符串序列化器现在根据字符类型进行模板化,分别编译单字节和双字节字符串的优化版本。这种策略在二进制大小上有所增加,但性能提升显著。同时,实现还高效处理了混合编码的情况,确保在常见情况下保持高度优化。
使用 SIMD 优化字符串序列化
为了加速字符串中需要转义的字符(如 " 或 \)的查找,V8 采用了基于字符串长度的两级策略:对于较长的字符串,使用硬件 SIMD 指令;对于较短的字符串,使用 SWAR(SIMD Within A Register)技术。无论哪种方法,都能高效地扫描字符串,并在没有特殊字符的情况下直接复制整个字符串。
快速路径中的“快速通道”
在快速路径中,V8 还引入了更快的“快速通道”。通过标记对象的隐藏类,如果对象的所有属性键都不是 Symbol,且所有属性都是可枚举的,并且没有需要转义的字符,那么在序列化相同隐藏类的对象时,可以直接复制所有键到字符串缓冲区,无需进一步检查。这一优化也被应用到 JSON.parse 中,用于快速键比较。
更快的双精度数转字符串算法
V8 将核心的 DoubleToString 算法从 Grisu3 升级为 Dragonbox,显著提升了数字到字符串的转换速度。这一优化不仅适用于 JSON.stringify,还惠及所有调用 Number.prototype.toString() 的代码。
优化临时缓冲区
为了避免内存管理带来的开销,V8 将旧的连续缓冲区替换为分段缓冲区。当一段缓冲区满时,只需分配新的段并继续写入,完全消除了昂贵的复制操作。
限制
新的快速路径专注于处理常见的简单情况。如果数据不符合这些条件,V8 会回退到通用序列化器以确保正确性。为了获得最佳性能,JSON.stringify 调用应满足以下条件:
- 不提供 replacer 或 space 参数;
- 序列化的对象应为简单的数据容器,且不包含自定义的 .toJSON() 方法;
- 对象不应包含类似数组的索引属性;
- 字符串应为简单的顺序字符串。
结论
通过从高层次逻辑到核心内存和字符处理操作的全面优化,V8 在 JetStream2 基准测试中实现了 JSON.stringify 性能的两倍以上提升。这些优化从 V8 13.8 版本(Chrome 138)开始可用。
评论总结
JSON编码对NodeJS进程间通信的阻碍
- hinkley指出,JSON编码在NodeJS中严重影响了进程间通信,并提到将任务转移到其他线程以减少事件循环阻塞的做法反而增加了主线程的CPU负载。
- 引用:"JSON encoding is a huge impediment to interprocess communication in NodeJS."
- 引用:"Sooner or later it seems like everyone gets the idea of reducing event loop stalls in their NodeJS code by trying to offload it to another thread, only to discover they’ve tripled the CPU load in the main thread."
分段缓冲区方法的优势
- MutedEstate45赞赏分段缓冲区方法,认为它类似于用户态中手动实现的rope数据结构,但更简洁且原生支持。
- 引用:"It's basically the rope data structure trick I used to hand-roll in userland with libraries like fast-json-stringify, now native and way cleaner."
- 引用:"Any replacer, space, or custom .toJSON() kicks you back to the slow path?"
浮点数序列化性能的提升
- jonas21对浮点数序列化性能的显著提升表示惊讶,并提供了相关性能数据的链接。
- 引用:"The part that was most surprising to me was how much the performance of serializing floating-point numbers has improved, even just in the past decade."
安全问题的担忧
- iouser提出,新的解决方案可能存在安全隐患,建议进行安全测试以避免未来出现CVE漏洞。
- 引用:"Did you run any tests/regressions against the security problems that are common with parsers?"
- 引用:"Seems like the solution might be at risk of creating CVEs later."
序列化副作用的疑问
- taeric对序列化过程中可能产生的副作用表示困惑,询问是否存在常见的副作用类别。
- 引用:"I confess that I'm at a bit of a loss to know what sort of side effects would be common when serializing something?"
- 引用:"Is there an obvious class of reasons for this that I'm just accidentally ignoring right off?"
对V8引擎的赞扬
- monster_truck认为V8引擎的性能令人惊叹,JavaScript的速度已经达到了极高的水平。
- 引用:"I don't think v8 gets enough praise. It is fucking insane how fast javascript can be these days."
SWAR转义算法的相似性
- ot指出,V8的SWAR转义算法与其在Folly JSON中实现的算法非常相似,并提供了相关代码链接。
- 引用:"The SWAR escaping algorithm is very similar to the one I implemented in Folly JSON a few years ago."
- 引用:"The latter works on 8 byte words instead of 4 bytes, and it also returns the position of the first byte that needs escaping."
底层临时缓冲区的优化
- pyrolistical提到,优化底层临时缓冲区可能意味着使用数组列表而非普通数组。
- 引用:"So array list instead of array?"