在之前的时长服务(duration)之前相关的问题记录里面,提到了 JSON Unmarshal 大对象

本来以为 标准库的JSON Unmarshal 效率会很高,一般对性能的优化也不会考虑到序列化这一块儿。结果当我

主要对比一下主流的几个golang的JSON库(选择了部分库对比研究)

json库名 Star
官方标准 JSON Unmarshal
tidwall/gjson 8.5k
tinylib/msgp 1.4k
buger/jsonparser 4k

标准库 JSON Unmarshal

先来看一个例子

源码分析(go version go1.16.5 )

JSON 解析库

  1. func Unmarshal(data []byte, v interface{})

在标准库JSON解析前会去调用reflect.ValueOf来获取参数 v 的反射对象,以此来判断以哪种方式来进行解析

  1. func (d *decodeState) value(v reflect.Value) error {
  2. switch d.opcode {
  3. default:
  4. panic(phasePanicMsg)
  5. case scanBeginArray: // 数组
  6. if v.IsValid() {
  7. if err := d.array(v); err != nil {
  8. return err
  9. }
  10. } else {
  11. d.skip()
  12. }
  13. d.scanNext()
  14. case scanBeginObject: // 结构体或者map
  15. if v.IsValid() {
  16. if err := d.object(v); err != nil {
  17. return err
  18. }
  19. } else {
  20. d.skip()
  21. }
  22. d.scanNext()
  23. case scanBeginLiteral: // int、string、float 等
  24. // All bytes inside literal return scanContinue op code.
  25. start := d.readIndex()
  26. d.rescanLiteral()
  27. if v.IsValid() {
  28. if err := d.literalStore(d.data[start:d.readIndex()], v, false); err != nil {
  29. return err
  30. }
  31. }
  32. }
  33. return nil
  34. }

看看解析Object

  1. func (d *decodeState) object(v reflect.Value) error {
  2. // Check for unmarshaler.
  3. u, ut, pv := indirect(v, false)
  4. // ...
  5. // Decoding into nil interface? Switch to non-reflect code.
  6. if v.Kind() == reflect.Interface && v.NumMethod() == 0 {
  7. oi := d.objectInterface()
  8. v.Set(reflect.ValueOf(oi))
  9. return nil
  10. }
  11. var fields structFields
  12. // Check type of target:
  13. // struct or
  14. // map[T1]T2 where T1 is string, an integer type,
  15. // or an encoding.TextUnmarshaler
  16. switch v.Kind() {
  17. case reflect.Map:
  18. // Map key must either have string kind, have an integer kind,
  19. // or be an encoding.TextUnmarshaler.
  20. switch t.Key().Kind() {
  21. // 如果是map的话还要对key进行分类
  22. case reflect.String,
  23. reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
  24. reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
  25. default:
  26. if !reflect.PtrTo(t.Key()).Implements(textUnmarshalerType) {
  27. d.saveError(&UnmarshalTypeError{Value: "object", Type: t, Offset: int64(d.off)})
  28. d.skip()
  29. return nil
  30. }
  31. }
  32. if v.IsNil() {
  33. v.Set(reflect.MakeMap(t))
  34. }
  35. case reflect.Struct
  36. // 缓存结构体的字段到 fields 对象中
  37. fields = cachedTypeFields(t)
  38. // ok
  39. default:
  40. d.saveError(&UnmarshalTypeError{Value: "object", Type: t, Offset: int64(d.off)})
  41. d.skip()
  42. return nil
  43. }
  44. var mapElem reflect.Value
  45. origErrorContext := d.errorContext
  46. for {
  47. // Read opening " of string key or closing }.
  48. d.scanWhile(scanSkipSpace)
  49. if d.opcode == scanEndObject {
  50. // closing } - can only happen on first iteration.
  51. break
  52. }
  53. if d.opcode != scanBeginLiteral {
  54. panic(phasePanicMsg)
  55. }
  56. // Read key.
  57. // 循环解析JSON字符串中的k,v
  58. start := d.readIndex()
  59. d.rescanLiteral()
  60. // 获取k
  61. item := d.data[start:d.readIndex()]
  62. key, ok := unquoteBytes(item)
  63. if !ok {
  64. panic(phasePanicMsg)
  65. }
  66. // Figure out field corresponding to key.
  67. var subv reflect.Value
  68. destring := false // whether the value is wrapped in a string to be decoded first
  69. if v.Kind() == reflect.Map {
  70. elemType := t.Elem()
  71. if !mapElem.IsValid() {
  72. mapElem = reflect.New(elemType).Elem()
  73. } else {
  74. mapElem.Set(reflect.Zero(elemType))
  75. }
  76. subv = mapElem
  77. } else {
  78. var f *field
  79. if i, ok := fields.nameIndex[string(key)]; ok {
  80. // Found an exact name match.
  81. f = &fields.list[i]
  82. } else {
  83. // Fall back to the expensive case-insensitive
  84. // linear search.
  85. for i := range fields.list {
  86. ff := &fields.list[i]
  87. if ff.equalFold(ff.nameBytes, key) {
  88. f = ff
  89. break
  90. }
  91. }
  92. }
  93. // ...
  94. }
  95. // Read : before value.
  96. if d.opcode == scanSkipSpace {
  97. d.scanWhile(scanSkipSpace)
  98. }
  99. if d.opcode != scanObjectKey {
  100. panic(phasePanicMsg)
  101. }
  102. d.scanWhile(scanSkipSpace)
  103. if destring { // 设置v值
  104. switch qv := d.valueQuoted().(type) {
  105. case nil:
  106. if err := d.literalStore(nullLiteral, subv, false); err != nil {
  107. return err
  108. }
  109. // ...
  110. default:
  111. d.saveError(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal unquoted value into %v", subv.Type()))
  112. }
  113. } else {
  114. // 数组或对象会递归调用 value 方法
  115. if err := d.value(subv); err != nil {
  116. return err
  117. }
  118. }
  119. // Write value back to map;
  120. // if using struct, subv points into struct already.
  121. if v.Kind() == reflect.Map {
  122. kt := t.Key()
  123. var kv reflect.Value
  124. switch {
  125. case reflect.PtrTo(kt).Implements(textUnmarshalerType):
  126. kv = reflect.New(kt)
  127. if err := d.literalStore(item, kv, true); err != nil {
  128. return err
  129. }
  130. kv = kv.Elem()
  131. case kt.Kind() == reflect.String:
  132. kv = reflect.ValueOf(key).Convert(kt)
  133. default:
  134. // ...
  135. }
  136. if kv.IsValid() {
  137. v.SetMapIndex(kv, subv)
  138. }
  139. }
  140. // 遇到 } 最后退出循环
  141. if d.opcode == scanSkipSpace {
  142. d.scanWhile(scanSkipSpace)
  143. }
  144. // Reset errorContext to its original state.
  145. // Keep the same underlying array for FieldStack, to reuse the
  146. // space and avoid unnecessary allocs.
  147. d.errorContext.FieldStack = d.errorContext.FieldStack[:len(origErrorContext.FieldStack)]
  148. d.errorContext.Struct = origErrorContext.Struct
  149. if d.opcode == scanEndObject {
  150. break
  151. }
  152. if d.opcode != scanObjectValue {
  153. panic(phasePanicMsg)
  154. }
  155. }
  156. return nil
  157. }

大概会做这么几件事

  1. 缓存结构体
  2. 循环遍历结构体对象
  3. 找到结构体中的 key 值之后再找到结构体中同名字段类型
  4. 递归调用 value 方法反射设置结构体对应的值
  5. 直到遍历到 JSON 中结尾 }结束循环。

Unmarshal 源码中使用了大量的反射来获取字段值,而且还有递归调用来获取反射值,如果是多层嵌套的JSON的话。性能会更差

GJSON

对比官方的JSON库,GJSON 优雅太多了,因为 Golang中简单的数据结构可使用map[string]interface{},但是嵌套复杂的结构需要预定义struct结构体。用json.Unmarshal把数据解析到结构体中,然后get到值,虽然确实会清晰一些。比如不看文档的情况下,和其他系统做交互的情况下。可以有一个清晰的结构体。但是GJSON的代码量会少很多,这也算是它的优势吧。

源自官方demo

  1. package main
  2. import "github.com/tidwall/gjson"
  3. const json = `{"name":{"first":"Janet","last":"Prichard"},"age":47}`
  4. func main() {
  5. value := gjson.Get(json, "name.last")
  6. println(value.String())
  7. }

key可以包含特殊的通配符’*’和’?’

比如 有这么一个json串

  1. {
  2. "name": {"first": "Tom", "last": "Anderson"},
  3. "age":37,
  4. "children": ["Sara","Alex","Jack"],
  5. "fav.movie": "Deer Hunter",
  6. "friends": [
  7. {"first": "Dale", "last": "Murphy", "age": 44, "nets": ["ig", "fb", "tw"]},
  8. {"first": "Roger", "last": "Craig", "age": 68, "nets": ["fb", "tw"]},
  9. {"first": "Jane", "last": "Murphy", "age": 47, "nets": ["ig", "tw"]}
  10. ]
  11. }
  1. json := `{
  2. "name":{"first":"Tom", "last": "Anderson"},
  3. "age": 37,
  4. "children": ["Sara", "Alex", "Jack"]
  5. }`
  6. fmt.Println("third child*:", gjson.Get(json, "child*.2"))
  7. fmt.Println("first c?ild:", gjson.Get(json, "c?ildren.0"))
  8. // child*.2:首先child*匹配children,.2读取第 3 个元素; 此例子来源于第三方库

来看看get源码
func Get 有2个参数,一个是string json,一个是path(就好像和一个树的路径去匹配一样,)

  1. func Get(json, path string) Result {
  2. // 当path大于1时才去解析
  3. if len(path) > 1 {
  4. if !DisableModifiers {
  5. if path[0] == '@' {
  6. // possible modifier
  7. var ok bool
  8. var npath string
  9. var rjson string
  10. npath, rjson, ok = execModifier(json, path)
  11. if ok {
  12. path = npath
  13. // 会以. | 来对剩下的path做切割
  14. if len(path) > 0 && (path[0] == '|' || path[0] == '.') {
  15. res := Get(rjson, path[1:])
  16. res.Index = 0
  17. return res
  18. }
  19. return Parse(rjson)
  20. }
  21. }
  22. }
  23. if path[0] == '[' || path[0] == '{' {
  24. // using a subselector path
  25. kind := path[0]
  26. var ok bool
  27. var subs []subSelector
  28. subs, path, ok = parseSubSelectors(path)
  29. if ok {
  30. if len(path) == 0 || (path[0] == '|' || path[0] == '.') {
  31. var b []byte
  32. b = append(b, kind)
  33. var i int
  34. for _, sub := range subs {
  35. res := Get(json, sub.path)
  36. if res.Exists() {
  37. if i > 0 {
  38. b = append(b, ',')
  39. }
  40. if kind == '{' {
  41. if len(sub.name) > 0 {
  42. if sub.name[0] == '"' && Valid(sub.name) {
  43. b = append(b, sub.name...)
  44. } else {
  45. b = appendJSONString(b, sub.name)
  46. }
  47. } else {
  48. last := nameOfLast(sub.path)
  49. if isSimpleName(last) {
  50. b = appendJSONString(b, last)
  51. } else {
  52. b = appendJSONString(b, "_")
  53. }
  54. }
  55. b = append(b, ':')
  56. }
  57. var raw string
  58. if len(res.Raw) == 0 {
  59. raw = res.String()
  60. if len(raw) == 0 {
  61. raw = "null"
  62. }
  63. } else {
  64. raw = res.Raw
  65. }
  66. b = append(b, raw...)
  67. i++
  68. }
  69. }
  70. b = append(b, kind+2)
  71. var res Result
  72. res.Raw = string(b)
  73. res.Type = JSON
  74. if len(path) > 0 {
  75. res = res.Get(path[1:])
  76. }
  77. res.Index = 0
  78. return res
  79. }
  80. }
  81. }
  82. }
  83. var i int
  84. var c = &parseContext{json: json}
  85. if len(path) >= 2 && path[0] == '.' && path[1] == '.' {
  86. c.lines = true
  87. parseArray(c, 0, path[2:])
  88. } else {
  89. for ; i < len(c.json); i++ {
  90. if c.json[i] == '{' {
  91. i++
  92. parseObject(c, i, path)
  93. break
  94. }
  95. if c.json[i] == '[' {
  96. i++
  97. parseArray(c, i, path)
  98. break
  99. }
  100. }
  101. }
  102. if c.piped {
  103. res := c.value.Get(c.pipe)
  104. res.Index = 0
  105. return res
  106. }
  107. fillIndex(json, c)
  108. return c.value
  109. }

jsonparser

jsonparser 的入参 也是传入一个 JSON 的 byte 切片,以及可以通过传入多个 key 值来快速定位到相应的值,并返回。

jsonparser之所以有这么快的速度

  • 它不解析完整的内容,仅仅解析你指定的字段
  • 在字节级别上操做json,提供指向原始数据结构的指针,无内存分配;
  • 没有自动类型转化,默认状况下,全部内容都是字节切片,可是它提供了值类型,所以你能够本身进行转化

使用方式(来自官方demo)

  1. data := []byte(`{
  2. "person": {
  3. "name": {
  4. "first": "Leonid",
  5. "last": "Bugaev",
  6. "fullName": "Leonid Bugaev"
  7. },
  8. "github": {
  9. "handle": "buger",
  10. "followers": 109
  11. },
  12. "avatars": [
  13. { "url": "https://avatars1.githubusercontent.com/u/14009?v=3&s=460", "type": "thumbnail" }
  14. ]
  15. },
  16. "company": {
  17. "name": "Acme"
  18. }
  19. }`)
  20. jsonparser.Get(data, "person", "name", "fullName")

它只需要遍历一次 JSON字符串即可实现获取多个值的操作。

如果只是单纯的使用get

  1. func Get(data []byte, keys ...string) (value []byte, dataType ValueType, offset int, err error) {
  2. a, b, _, d, e := internalGet(data, keys...)
  3. return a, b, d, e
  4. }

第一个参数 value是一个[]byte类型,实际取值需要手动将其转换为对应的类型。而且还有根据索引去获取值。甚至是遍历key。

性能对比

解析190字节的字符串

库名 bytes/op bytes/op allocs/op
encoding/json struct 3314 ns/op 744 B/op 15 allocs/op
encoding/json interface{} 3075 ns/op 1377 B/op 32 allocs/op
buger/jsonparser 671.4 ns/op 0 0
buger/jsonparser (EachKey API) 539.9 ns/op 0 0
pquerna/ffjson 3758 ns/op 620 B/op 15 allocs/op
msgp 291.9 ns/op 208 B/op 5 allocs/op

解析 2.4kb 左右的字符串

库名 bytes/op bytes/op allocs/op
encoding/json struct 26198 ns/op 1064 B/op 206 allocs/op
encoding/json interface{} 26674 ns/op 10380 B/op 225 allocs/op
buger/jsonparser 6335 ns/op 0 0
buger/jsonparser (EachKey API) 4616 ns/op 112 B/op 2 allocs/op
pquerna/ffjson 20298 ns/op 856 B/op 20 allocs/op
msgp 202.3 ns/op 112 B/op 4 allocs/op

解析24k左右的字符串

库名 bytes/op bytes/op allocs/op
encoding/json struct 334067 ns/op 6101 B/op 211 allocs/op
encoding/json interface{} 412735 ns/op 210656 B/op 2875 allocs/op
buger/jsonparser 46780 ns/op 0 0
pquerna/ffjson 137213 ns/op 5813 B/op 206 allocs/op
msgp 209.8 ns/op 128 B/op 4 allocs/op

总结

使用反射的性能基本上是比较低的,比如JSON官方库,就使用了大量的反射。其他开源的序列化的库通过遍历字节来解析,使得性能高了很多。
有的库根本不需要定义结构体来映射,比如GJSON,可以直接根据JSON字符串来取得值,还支持模糊查找。