json.Marshal 比 jsoniter.Marshal 慢因前者每次调用都需反射解析结构体字段和标签,后者编译期生成代码或缓存反射结果,实测快2–5倍;公平基准测试需预热、固定输入、禁用GC干扰。
为什么 json.Marshal 比 jsoniter.Marshal 慢?
默认 encoding/json 使用反射 + 接口断言,每次序列化都要动态解析结构体字段、类型和标签,开销大;jsoniter 在编译期生成代码(或运行时缓存反射结果),跳过重复的元数据查找。实测在中等复杂度结构体(如含嵌套 map、slice、指针字段)下,jsoniter 通常快 2–5 倍。
实操建议:
- 用
go test -bench=.对比基准测试,确保BenchmarkJSONMarshal和BenchmarkJSONIterMarshal使用完全相同的输入数据和结构体实例 - 避免在 benchmark 中混用
json.RawMessage或预序列化字段——这会掩盖真实差异 - 注意
jsoniter.ConfigCompatibleWithStandardLibrary模式下兼容性优先,性能略低于原生jsoniter配置
如何写一个公平的 JSON 序列化性能 benchmark?
常见错误是让 encoding/json 反复解析 struct 标签,而 jsoniter 复用缓存,导致结果失真。关键在于控制变量:预热、固定输入、禁用 GC 干扰。
实操建议:
- 在
Benchmark函数开头调用一次json.Marshal和jsoniter.Marshal,触发初始化和反射缓存 - 用
var input = &MyStruct{...}定义全局变量,避免每次迭代重新分配内存 - 在循环内加
b.ReportAllocs(),观察是否因临时字符串拼接或 map 扩容引入额外分配 - 使用
runtime.GC()和runtime.ReadMemStats()(仅调试)确认无内存抖动干扰耗时
easyjson 生成的 MarshalJSON 方法为什么更快但更难维护?
easyjson 在 go generate 阶段为每个 struct 生成专用的、零反射的序列化函数,直接操作字节流,几乎没有抽象层开销。但它要求你显式为每个需序列化的 struct 添加 //easyjson:json 注释,并在修改字段后重新生成代码。