V8 如何把 JSON.stringify 性能提升 2 倍
JSON.stringify 应该是 JavaScript 里用得最多的函数之一了。
API 响应要序列化,日志要格式化,数据要存 localStorage,调试要打印对象......几乎每个项目都离不开它。
但说实话,用的时候很少会想"这玩意儿快不快"。反正就是调一下,能用就行。
V8 团队显然不这么想。V8 是 Chrome 和 Node.js 背后的 JavaScript 引擎,你写的每一次 JSON.stringify,最后都要靠它来跑。2025 年 8 月,他们发了篇博客,讲了怎么把 JSON.stringify 的性能提升到原来的 2 倍以上。
这篇文章拆解一下他们做了什么。
读者导航:不懂 V8 也能看
先记住三句话就够了:
- 绝大多数优化都在"走捷径":先判断输入是不是"简单、可预测"的对象,是的话走更快的路径。
- 很多名词听着硬,其实都在做同一件事:减少检查、减少函数调用、让 CPU 一次干更多活、减少内存搬运。
- 你能做的配合也很简单 :少用会触发副作用的写法(getter、
toJSON、格式化参数),保持数据对象"干净"。
下面遇到生词可以先跳过,看完"对开发者的启示"再回头补。
优化的前提:无副作用检测
JSON.stringify 慢在哪?
一个重要原因是它要处理各种边界情况:对象可能有 toJSON 方法,属性可能是 getter,可能有循环引用......这些都可能产生副作用,导致序列化结果不可预测。
V8 的第一步优化是:检测对象是否"干净"。
如果能确定序列化过程不会触发任何副作用,就可以走一条快速路径,跳过大量的安全检查。
"if we can guarantee that serializing an object will not trigger any side effects, we can use a much faster, specialized implementation."
这条快速路径用迭代替代了递归,好处有两个:
- 不用担心栈溢出(深层嵌套对象)
- 减少函数调用开销
字符串处理:双版本编译
JavaScript 字符串有两种内部表示:单字节(Latin-1)和双字节(UTF-16)。
以前 V8 用统一的方式处理,现在编译了两个特化版本的序列化器,分别针对这两种编码优化。可以简单理解成:如果字符串全是英文数字,就走"单字节快车道";如果包含中文表情等,就走"UTF-16 车道"。
遇到混合编码的情况(比如一个对象里既有纯 ASCII 字符串,又有中文),会在执行时动态切换。
这种"按需特化"的思路在编译器优化里很常见,但用在 JSON 序列化上还是挺有意思的。
SIMD 加速字符扫描
序列化字符串时,需要扫描哪些字符需要转义(比如 \n、\t、")。
V8 用了两种硬件加速策略:
- SIMD 指令:对于较长的字符串,一次处理多个字符(你可以理解成"把 16 个字节打包一起扫一遍")
- SWAR 技术:对于较短的字符串,用位运算在普通寄存器上并行处理(SIMD 的"轻量版")
SWAR(SIMD Within A Register)是个挺老的技术,思路是把一个 64 位寄存器当成 8 个 8 位的"小寄存器"用,通过位运算实现并行。
举个例子,判断一个字节是否需要转义,可以这样:
javascript
// 伪代码示意
// 需要转义的字符:< 0x20 或 == 0x22(") 或 == 0x5C(\)
function needsEscape(byte) {
return byte < 0x20 || byte === 0x22 || byte === 0x5C;
}
用 SWAR 可以一次判断 8 个字节,只要用合适的掩码和位运算。
Hidden Class 标记
V8 内部用 Hidden Class(隐藏类)来优化对象属性访问。你可以把它理解成"对象结构的身份证":同一类对象(同样的字段、同样的顺序)会复用同一个结构描述。
这次优化加了一个新标记:fast-json-iterable。
当一个对象的属性满足特定条件时,就给它打上这个标记。下次序列化同类对象时,直接跳过验证检查。
这就是典型的"用空间换时间"------在对象上多存一个标记,换来后续操作的加速。
数字转字符串:换算法
把数字转成字符串也是序列化的一部分。
V8 以前用 Grisu3 算法,现在换成了 Dragonbox。
你不需要理解算法细节,只要知道:Dragonbox 能更快、更稳定地把浮点数转成最短且精确的十进制表示。这个改动不只影响 JSON 序列化,所有 Number.toString() 都能受益。
内存管理:分段缓冲区
以前序列化大对象时,V8 用一块连续内存作为缓冲区。对象越大,缓冲区就要不断扩容,每次扩容都要重新分配和复制。
新实现用分段缓冲区(segmented buffer),多个小块链起来用,避免了昂贵的重分配。直觉上就是:不强求"一次申请一大块",而是"够用就多挂一块"。
这个思路和 Linux 内核的 sk_buff 链表类似------不追求内存连续,换来分配效率。
快速路径的适用条件
不是所有 JSON.stringify 调用都能走快速路径。需要满足:
-
不传 replacer 和 space 参数
javascript// 可以走快速路径 JSON.stringify(obj); // 不行 JSON.stringify(obj, null, 2); JSON.stringify(obj, ['name', 'age']);这里的
replacer/space分别是"过滤/改写字段的回调或白名单"和"为了好看而加的缩进"。它们会让序列化过程更复杂,所以很难走最激进的优化路径。 -
纯数据对象
- 没有
toJSON方法 - 没有 getter
- 没有 Symbol 属性
- 没有
-
字符串键
- 所有属性名都是字符串(不是数字下标)
-
简单字符串值
- 字符串值本身没有特殊情况
实际项目里,大部分序列化场景都满足这些条件。API 返回的纯 JSON 数据、配置对象、日志数据......基本都能用上。
什么时候能用?
Chrome 138 / V8 13.8 开始可用。
Node.js 的话,需要等对应的 V8 版本合入。目前最新的 Node.js 22 用的是 V8 12.x,还得等一等。
对开发者的启示
虽然优化是 V8 团队做的,但有几点可以参考:
1. 保持对象"干净"
避免在需要序列化的对象上加 getter 或 toJSON 方法。如果必须用,考虑在序列化前转换成纯数据对象。
javascript
// 不太好
class User {
get fullName() {
return this.firstName + ' ' + this.lastName;
}
}
// 更好
const user = {
firstName: 'John',
lastName: 'Doe',
fullName: 'John Doe' // 直接计算好
};
2. 大量序列化时考虑结构一致性
V8 的 Hidden Class 优化依赖对象结构一致。如果你要序列化大量对象,保持它们的属性顺序和类型一致。
javascript
// 好
const users = [
{ name: 'Alice', age: 25 },
{ name: 'Bob', age: 30 },
{ name: 'Charlie', age: 35 }
];
// 不太好(age 类型不一致)
const users = [
{ name: 'Alice', age: 25 },
{ name: 'Bob', age: '30' }, // 字符串
{ name: 'Charlie', age: null }
];
3. 避免无意义的格式化
开发环境用 JSON.stringify(obj, null, 2) 看着舒服,但这会跳过快速路径。生产环境记得去掉。
javascript
// 开发环境
console.log(JSON.stringify(data, null, 2));
// 生产环境
console.log(JSON.stringify(data));
总结
- 分析热点:找到可以优化的场景(无副作用对象)
- 特化路径:针对常见情况走快速路径,边界情况走通用路径
- 硬件加速:用 SIMD 和 SWAR 提升字符处理速度
- 利用已有信息:通过 Hidden Class 标记避免重复验证
- 改进算法:Dragonbox 替代 Grisu3
- 优化内存:分段缓冲区避免重分配
这些技术单拿出来都不新鲜,但组合起来能把一个已经很成熟的 API 性能翻倍,还是挺厉害的。
对我们写代码的人来说,最大的收获可能是:写"干净"的代码不只是为了可读性,有时候还能让引擎更好地优化。
延伸阅读
如果你觉得这篇文章有帮助,欢迎关注我的 GitHub,下面是我的一些开源项目:
Claude Code Skills (按需加载,意图自动识别,不浪费 token,介绍文章):
- code-review-skill - 代码审查技能,覆盖 React 19、Vue 3、TypeScript、Rust 等约 9000 行规则(详细介绍)
- 5-whys-skill - 5 Whys 根因分析,说"找根因"自动激活
- first-principles-skill - 第一性原理思考,适合架构设计和技术选型
全栈项目(适合学习现代技术栈):
- prompt-vault - Prompt 管理器,用的都是最新的技术栈,适合用来学习了解最新的前端全栈开发范式:Next.js 15 + React 19 + tRPC 11 + Supabase 全栈示例,clone 下来配个免费 Supabase 就能跑
- chat_edit - 双模式 AI 应用(聊天+富文本编辑),Vue 3.5 + TypeScript + Vite 5 + Quill 2.0 + IndexedDB