json.Marshal 比 jsoniter.Marshal 慢因前者每次调用都需反射解析结构体字段和标签,后者编译期生成代码或缓存反射结果,实测快2–5倍;公平基准测试需预热、固定输入、禁用GC干扰。

为什么 json.Marshal 比 jsoniter.Marshal 慢?
默认 encoding/json 使用反射 + 接口断言,每次序列化都要动态解析结构体字段、类型和标签,开销大;jsoniter 在编译期生成代码(或运行时缓存反射结果),跳过重复的元数据查找。实测在中等复杂度结构体(如含嵌套 map、slice、指针字段)下,jsoniter 通常快 2–5 倍。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 用
go test -bench=.对比基准测试,确保BenchmarkJSONMarshal和BenchmarkJSONIterMarshal使用完全相同的输入数据和结构体实例 - 避免在 benchmark 中混用
json.RawMessage或预序列化字段——这会掩盖真实差异 - 注意
jsoniter.ConfigCompatibleWithStandardLibrary模式下兼容性优先,性能略低于原生jsoniter配置
如何写一个公平的 JSON 序列化性能 benchmark?
常见错误是让 encoding/json 反复解析 struct 标签,而 jsoniter 复用缓存,导致结果失真。关键在于控制变量:预热、固定输入、禁用 GC 干扰。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 在
Benchmark函数开头调用一次json.Marshal和jsoniter.Marshal,触发初始化和反射缓存 - 用
var input = &MyStruct{...}定义全局变量,避免每次迭代重新分配内存 - 在循环内加
b.ReportAllocs(),观察是否因临时字符串拼接或 map 扩容引入额外分配 - 使用
runtime.GC()和runtime.ReadMemStats()(仅调试)确认无内存抖动干扰耗时
easyjson 生成的 MarshalJSON 方法为什么更快但更难维护?
easyjson 在 go generate 阶段为每个 struct 生成专用的、零反射的序列化函数,直接操作字节流,几乎没有抽象层开销。但它要求你显式为每个需序列化的 struct 添加 //easyjson:json 注释,并在修改字段后重新生成代码。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 只对高频序列化的核心模型(如 API 响应体、日志事件)启用
easyjson,非关键路径不必过度优化 - 生成命令必须包含
-all和-output_filename,否则可能漏掉嵌套结构体的实现 - 注意
easyjson默认不支持json:",omitempty, string"中的stringtag,需手动补丁或改用jsoniter - CI 中加入
go:generate检查,防止提交未更新的 generated 文件
什么时候不该优化 JSON 序列化性能?
如果单次 HTTP 响应耗时中,json.Marshal 占比低于 5%,或 QPS 小于 1000,优化收益极小。真正的瓶颈常在数据库查询、外部 RPC 或锁竞争上。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 先用
pprof抓 CPU profile,过滤出encoding/json.*的实际火焰图占比,再决定是否投入 - 避免在
http.HandlerFunc中直接 benchmark —— 网络栈、TLS、gzip 压缩等环节的开销远大于序列化本身 - 若服务已用
msgpack或protobuf替代 JSON,序列化性能就不是瓶颈,而是协议选型问题
最易被忽略的一点:结构体字段顺序会影响 encoding/json 的反射遍历路径长度,但对 jsoniter 和 easyjson 无影响——所以别为了“优化”刻意重排字段,除非你确定用的是标准库且 profile 显示它真成了热点。











