Golang - json 性能分析
Json 作为一种重要的数据格式,具有良好的可读性以及自描述性,广泛地应用在各种数据传输场景中。Go 语言里面原生支持了这种数据格式的序列化以及反序列化,内部使用反射机制实现,性能有点差,在高度依赖 json 解析的应用里,往往会成为性能瓶颈,从下面的火焰图中可以发现在业务逻辑处理中,有一半多的性能消耗都是在 JSON 解析过程中,有很多第三方库帮忙解决了这个问题,接下来分析一下常用的几个库。
encoding/json
- 官方提供的标准json, 实现RFC 7159中定义的JSON编码和解码。
- 使用的时候需要预定义struct,原理是通过reflection和interface来完成工作,性能低。
常用接口:
- func Marshal(v interface{}) ([]byte, error) 生成JSON
- func Unmarshal(data []byte, v interface{}) error 解析JSON到struct
json-iterator
使用modern-go/reflect2来优化反射性能,通过大幅度减少反射操作来提高速度。
- 完全兼容json标准库,也就是API用法完全一样,原有代码不需要改动。
- 提供了一个兼容模式(需要手动开启),可以自动转换字符串/数字弱类型问题,可以转换[]与{}弱类型问题(PHP中的array问题)。
Github: https://github.com/json-iterator/go
package main
import (
"fmt"
jsoniter "github.com/json-iterator/go"
"github.com/json-iterator/go/extra"
)
var json = jsoniter.ConfigCompatibleWithStandardLibrary
func init() {
// RegisterFuzzyDecoders decode input from PHP with tolerance.
// It will handle string/number auto conversation, and treat empty [] as empty struct.
extra.RegisterFuzzyDecoders() // 手动开启兼容模式
}
type StdStruct struct {
Age int `json:"age"`
}
func main() {
s := "{\"age\": \"10\"}"
d := &StdStruct{}
if err := json.Unmarshal([]byte(s), d); err != nil {
fmt.Println(err)
} else {
fmt.Println(d.Age) // 开启兼容模式后,可以解析出字符串下的10
}
}
注意:只需要在main文件里通过init开启1次PHP兼容模式即可,后续引入的模块不需要重复开启。
以下是对一个对象序列化1000次,所用的耗时对比:
easyjson
easyjson 的思想是增加一个预编译的过程,预先生成对应结构的序列化反序列化代码,除此之外,easyjson 还放弃了一些原生库里面支持的一些不必要的特性,比如:key 类型声明,key 大小写不敏感等等,以达到更高的性能
生成代码执行 easyjson -all <file.go> 即可,如果不指定 -all 参数,只会对带有 //easyjson:json 的结构生成代码
//easyjson:json
type A struct {
Bar string
}
性能测试
从上面的结果可以看出来:
- easyjson 无论是序列化还是反序列化都是最优的,序列化提升了1倍,反序列化提升了3倍
- jsoniter 性能也很好,接近于easyjson,关键是没有预编译过程,100%兼容原生库
- ffjson 的序列化提升并不明显,反序列化提升了1倍
- codecjson 和原生库相比,差不太多,甚至更差
- jsonparser 不太适合这样的场景,性能提升并不明显,而且没有反序列化
所以综合考虑,建议使用 jsoniter,如果追求极致的性能,考虑 easyjson。