Go JSON 序列化库实测:GB 级大 JSON,内存为什么只有 KB 级
2026/9/18 14:18:15 网站建设 项目流程

Go JSON 序列化库实测:GB 级大 JSON,内存为什么只有 KB 级

【免费下载链接】sonicA blazingly fast JSON serializing & deserializing library项目地址: https://gitcode.com/GitHub_Trending/sonic2/sonic

一份 JSON 文件涨到 GB 级,多数 Go JSON 库的第一个麻烦是内存:整个文档必须读进内存才开始解析,中间对象还会把内存再翻几倍。Sonic 走的是另一条路——作为一款 Go JSON 序列化/反序列化库,它边读边解析,处理 GB 级数据时常驻内存能压到 KB 级;基准测试里,635KB 大 JSON 解码达到 594 MB/s,编码超过 2000 MB/s。这篇讲清楚这些数字是怎么做到的,以及接入时该注意什么。

它是怎么做到的:一条 JSON 输入的三段旅程 ⚙️

Sonic 的核心设计是"按需解析":不要求 JSON 的每个字节都被完整读出来。一次完整的输入输出,可以拆成三段旅程。

Sonic 的整体架构:JIT、SIMD、AST 等模块协同工作

第一段:边读边解析,不把整个文档拉进内存

Sonic 的解码器可以直接挂在io.Reader上,从流里逐个取出下一个 JSON 值再解析。它的ast.Node也走惰性加载:不访问某个节点,就不去解析底层的值。于是"从一个大响应里只取一个字段"时,绝大部分数据根本不需要进内存。

打个比方,这就像看书时随手翻到哪页看哪页,而不是把整本书搬进办公室。

第二段:JIT 专属代码,给每个 struct 现场裁一件

反射(reflection)是在运行时动态查类型信息,代价是每次调用都有开销。Sonic 用 JIT(即时编译,通俗说就是运行时现场生成一小段专属代码)替代:运行时组装与目标 struct 匹配的字节码(汇编指令),再把这个结果以 Go 函数的形式缓存在堆外内存。首次走一遍编译,之后全是直连调用,省掉了接口封装和动态函数调用的损耗——相当于裁缝量体裁衣,而不是每次穿成衣再改。

JIT 解码器的实现可以延伸阅读:internal/decoder/jitdec/。

第三段:SIMD 与标量自适应,大数据并行、小数据省开销

SIMD(单指令多数据)是一组能同时处理多个数据的 CPU 指令,适合长字符串的整形、字符搜索。但它有代价:对很短或不规则的字符串,额外加载操作反而拖慢速度。Sonic 的做法是预处理判断(字符串长度、浮点精度等)后自动切换:大数据走 SIMD 并行,16 字节以下的短串直接走标量指令,两头都不吃亏。核心计算函数用 C/Clang 编写、经 LLVM 优化后,再转成 Go 运行时可加载的汇编。

传统做法为什么在大 JSON 面前翻车

把上面三段旅程反过来看,传统库的内存问题就有来源了。

  • 全量加载:整份文档必须先装进内存,数据量翻倍,内存跟着翻倍,GB 级文件很容易直接 OOM。
  • 中间对象复制:解析过程会创建大量 map、interface{} 中间结构,源缓冲区和解码对象同时驻留,内存常翻倍。
  • 反射与函数调用开销:靠反射取 schema 元信息本身就慢;改成逐字段组装函数后,又引入大量接口封装和无法内联的调用——实测中,随着 JSON 变深变大,这类库与 Sonic 的差距逐渐缩小、最终被反超。
  • 单键查找的副作用:某些库靠"跳过"省解析,但多键查找时同一路径被重复解析,速度甚至低于标准库。

数据规模越大,基于函数组装的库与 JIT 路线的差距越明显

实测数据:解码、编码、单键查找三组数字

以下数据来自仓库自带的基准测试(Go 1.17,i9-9880H)。

中型 JSON(13KB,300+ 键,6 层嵌套):

场景Sonicgjsonjsoniter标准库
绑定解码400.38 MB/s371.48 MB/s116.97 MB/s
绑定编码2079.26 MB/s649.93 MB/s792.52 MB/s
单键查找3975.78 MB/s1380.81 MB/s254.46 MB/s

大型 JSON(635KB,10000+ 键,6 层嵌套):解码 594 MB/s,编码超过 2000 MB/s,领先图中其他库。

635KB 大 JSON:Sonic 的编码、解码速率均领先

小型 JSON(400B,11 键,3 层嵌套):解码速度是标准库的 5 倍以上——第三段的标量指令路径在小数据上把额外开销压住了。

400B 小 JSON:Sonic 解码速度超过标准库 5 倍

5 分钟上手:安装并跑通第一个流式解析 📦

环境要求:Go 1.18~1.26(注意 Go 1.24.0 有已知兼容问题,换更高版本或加-ldflags="-checklinkname=0"构建);Linux / macOS / Windows;AMD64 或 ARM64(ARM64 需 Go 1.20+)。

安装命令:

go get github.com/bytedance/sonic

下面的最小示例演示流式解析:从同一字节流里连续解码出两个 JSON 值,内存里始终只保留当前值:

var o = map[string]interface{}{} var r = strings.NewReader(`{"a":"b"}{"1":"2"}`) dec := sonic.ConfigDefault.NewDecoder(r) dec.Decode(&o) dec.Decode(&o)

在不支持 Sonic 的环境中,它会自动回退到encoding/json,业务代码不用改。

它适合谁,接入时注意什么

适合的场景:

  • JSON 负载重的高并发服务。字节跳动的生产统计里,序列化/反序列化占 CPU 接近 10%,极端情况超过 40%,常是机器利用率的瓶颈。
  • 大 JSON 日志、超大 API 响应、单键查找(GetOne 实测 3975.78 MB/s)。
  • 内存受限、需要对大数据控制常驻内存的环境。

接入注意点:

  • 版本:Go 1.24.0 不可用,需绕开;ARM64 需 Go 1.20+。
  • 与标准库的行为差异:默认不转义 HTML、不排序键(都是为性能)。要严格兼容,改用ConfigStd
  • 字符串引用:解码出的无转义字符串默认直接引用原始缓冲区,省 CPU 但会连带持有原缓冲区;若长期缓存解码对象,建议开启CopyString
  • 巨型结构首次编译:嵌套极深的 struct 首次 JIT 编译较慢,可能拖慢首请求甚至触发超时,建议用PretouchMany()在初始化时预热。

如果 JSON 序列化/反序列化正在拖慢你的服务,或大文件总让内存告警,"流式 + JIT + SIMD/标量自适应"这套组合,是当前 Go JSON 库生态里实测数字最能打的答案。更多设计细节可看 中文介绍文档。

【免费下载链接】sonicA blazingly fast JSON serializing & deserializing library项目地址: https://gitcode.com/GitHub_Trending/sonic2/sonic

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询