确认瓶颈需用pprof看火焰图:若reflect.Value.Interface或encoding/json.(*encodeState).marshal占CPU>30%,才是序列化真慢;否则多为上游数据构造问题,如循环拼map[string]interface{}或json.RawMessage滥用。

Go里大对象JSON序列化慢,不是写法问题,而是标准库json.Marshal在字段多、嵌套深、含interface{}或指针时,反射开销和小对象分配会指数级放大。优化必须分两步:先确认瓶颈真在序列化,再选对方法——换库、改结构、或绕过JSON本身。
怎么确认是json.Marshal拖慢了?
别猜,用pprof看真实火焰图:
- 如果
reflect.Value.Interface或encoding/json.(*encodeState).marshal占CPU >30%,才是序列化真瓶颈 - 如果
runtime.mallocgc高但json函数占比低,说明上游构造数据时就在反复拼map[string]interface{}或json.RawMessage,得改数据组装逻辑 - 时间字段输出纳秒(如
sonic)或HTML转义(如jsoniter默认)导致前端解析失败,容易误判为“慢”,其实是格式不兼容
easyjson生成代码后调用方式不对,等于白装
easyjson不走反射,但要求严格调用路径:
- 结构体上方必须独占一行写
//easyjson:json,不能带空格或注释混写 - 运行
easyjson -all user.go生成user_easyjson.go,且该文件必须和原结构体在同一包 - 调用时不能写
json.Marshal(&u),要改用u.MarshalJSON()或easyjson.Marshal(&u) - 遇到
interface{}字段会panic,不是报错而是直接崩溃,需提前转成具体结构体或用*interface{}指针
jsoniter配对启用API实例,否则加速失效
jsoniter兼容标准库导入,但默认配置没开任何加速:
立即学习“go语言免费学习笔记(深入)”;
- 全局只创建一次
jsoniter.API实例,比如var json = jsoniter.ConfigCompatibleWithStandardLibrary,然后复用它 - 别在handler里每次调用都
jsoniter.Config{}.Froze(),这会重新初始化反射缓存,比原生还慢 - 可信数据场景可设
Unsafe=true跳过边界检查,提速20–40%,但用户直传的JSON绝对不能开 - 字段含
time.Time时,默认输出RFC3339字符串,若前端要时间戳,得手动注册encoder,不然序列化结果不符预期
真正省时间的,往往是绕过序列化本身
很多接口“慢”根本不在Marshal,而在构造阶段就埋了雷:
- 循环里拼
map[string]interface{},每轮都分配新map和string,换成预定义struct +omitempty标签 - 大量透传子JSON时,
json.RawMessage不做拷贝优化,直接用[]byte拼接更省 - 高频接口中,
bytes.Buffer或[]byte切片用sync.Pool管理,防止逃逸到堆上触发GC - 内部服务通信优先切
protobuf,体积小3–5倍、序列化快2–5倍,但对外API仍保持JSON,避免协议撕裂
最常被忽略的点:字段顺序。结构体里把高频字段放前面,能减少内存跳跃,CPU缓存命中率上升,连带序列化也快一点——这种优化不改一行逻辑,但压测时QPS可能差几百。


















