json.marshal在高频或复杂结构下性能差,主因是反射、类型判断和小对象分配;优化方案包括用easyjson生成无反射代码(吞吐提升2–5倍)、jsoniter配对启用并复用api实例,以及排查上游数据构造瓶颈。

标准库 json.Marshal 在高频或结构复杂场景下明显拖慢吞吐,根本原因不是写法错,而是它每次都要走反射查字段、做类型判断、分配小对象。换库或改写法前,先确认瓶颈真在序列化本身——很多“慢”实际出在上游数据构造环节。
为什么 json.Marshal 会突然变慢
不是所有结构体都触发同等开销。当出现以下情况时,性能下降会非常显著:
- 结构体字段超过 10 个,或嵌套深度 ≥3,反射遍历成本指数上升
- 传入
map[string]interface{}或interface{}类型,强制走最通用(也最慢)路径 - 字段含指针或
json.RawMessage,每次都要做 nil 检查和边界判断 -
pprof显示reflect.Value.Interface或encoding/json.(*encodeState).marshal占 CPU 高峰
用 easyjson 生成无反射代码
它不 runtime 查字段,而是在构建阶段把 MarshalJSON 方法硬编码进源码,实测吞吐提升 2–5 倍,GC 分配减少 90%+。但要注意生成逻辑和调用方式:
- 结构体上方加独占行注释:
//easyjson:json - 运行
easyjson -all user.go,生成user_easyjson.go - 调用时不用
json.Marshal(&u),改用u.MarshalJSON()或easyjson.Marshal(&u) - 不支持
interface{}字段嵌套,遇到会 panic,需提前 flatten 或改用指针字段
用 jsoniter 零修改接入但必须配对启用
API 完全兼容标准库,适合快速验证效果,但默认配置没开加速,白换等于没换:
- 导入
github.com/json-iterator/go,替换json.Marshal为jsoniter.Marshal - 必须全局复用一个
jsoniter.API实例,别在 hot path 里反复jsoniter.Config{...}.Froze() - 要提速得显式绑定类型:
jsoniter.RegisterTypeEncoder("User", &userEncoder{}),否则仍走反射 - 可信数据场景可启用
Unsafe=true,跳过部分边界检查,提速 20–40%,但禁止用于用户直传 JSON
别忽略上游数据构造这个真瓶颈
很多服务换了 sonic 或 easyjson 后 QPS 没涨,问题不在 Marshal 函数本身:
- 避免在循环里拼
map[string]interface{},改用预定义 struct +omitempty -
json.RawMessage不优化拷贝,大量透传子 JSON 时,直接用[]byte拼接更省 - 高频接口中,复用
bytes.Buffer或sync.Pool管理切片,防止短生命周期[]byte逃逸 - 时间字段默认输出纳秒时间戳(
sonic)、或 HTML 转义(jsoniter),和前端预期不符时,得手动注册 encoder
真正耗时的往往不是最后一行 Marshal 调用,而是前面几十行构造数据的逻辑。先跑 go tool pprof -http=:8080 binary 看火焰图,再决定动哪一层。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











