json.Marshal 慢因反射检查字段类型和频繁小对象分配;jsoniter 需显式绑定类型并启用 Unsafe 模式提速;sonic 最快但平台受限且需适配配置与错误处理。

json.Marshal 为什么慢?关键在反射和内存分配
Go 标准库 json.Marshal 默认走反射路径,每次序列化都要动态检查结构体字段、标签、类型,开销大;同时频繁触发小对象分配(如 map[string]interface{} 或嵌套 slice),加剧 GC 压力。这不是“写得不好”,而是设计取舍——通用性优先,性能让步。
- 结构体字段超过 10 个、嵌套深度 ≥3 时,反射成本明显上升
-
interface{}输入(比如map[string]interface{})几乎必然走最慢路径,无法内联或编译期优化 - 标准库不复用
bytes.Buffer底层,每次调用都新建,短生命周期切片易逃逸
jsoniter 可以零修改接入,但要注意 tag 和 unsafe 模式
jsoniter 兼容标准库 API,json.Marshal 替换为 jsoniter.ConfigCompatibleWithStandardLibrary.Marshal 就能跑,但默认配置没开加速。真正提速靠两件事:显式绑定类型 + 启用 unsafe 模式。
- 必须提前调用
jsoniter.RegisterTypeEncoder或使用jsoniter.RegisterFieldEncoder绑定结构体,否则仍走反射 - 启用
jsoniter.Config{Unsafe=true}后,会绕过部分边界检查,提升 20–40%,但要求输入数据可信(比如不来自用户直传的 raw JSON) - 结构体 tag 不支持
json:",string"这类强制字符串转换(标准库支持,jsoniter 需手动处理)
sonic 是目前最快的选项,但只支持 Linux/amd64 和 macOS/ARM64
sonic 用 C++ 编写核心解析器,通过 CGO 调用,序列化速度通常是标准库 3–5 倍。但它不是“装完就能用”——平台、构建方式、错误处理习惯都得适配。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 不支持 Windows(无预编译二进制)、不支持 32 位架构、ARM64 仅限 macOS(Linux ARM64 需自行编译)
- 必须用
sonic.ConfigFastest.Marshal,而不是直接调sonic.Marshal,否则退化到兼容模式 - 错误类型是
sonic.Error,不是error接口的普通实现,errors.Is或fmt.Errorf("...%w")可能失效 - 对
time.Time默认输出纳秒时间戳,和标准库的 RFC3339 字符串不一致,需显式注册 encoder
别只看 benchmark,真实瓶颈常在序列化前的数据准备
很多服务压测发现换 sonic 后 QPS 没涨多少,问题往往不在 Marshal 本身,而在上游:反复构造临时 map、未复用 struct 实例、JSON 字段冗余(比如带空 slice 或零值字段未忽略)。
- 用
go tool pprof -http=:8080 binary -gcflags="-m"看是否真有大量json.*函数逃逸 - 避免在 hot path 上做
map[string]interface{}拼装,改用预定义 struct +omitempty -
sonic和jsoniter都不优化json.RawMessage的拷贝,如果大量透传子 JSON,考虑用[]byte直接拼接
真正卡住吞吐的,常常是那几个没加 omitempty 的空字段,或者一次请求里重复 new 了 7 次同一个 config struct。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










