golang源码分析:go-json(2)

2023-09-06 19:19:33 浏览数 (1)

在分析完编码过程中的一些优化后,我们分析下解码过程中的优化。

1,类型到解码函数的映射,由map改成slice,这个优化和编码一样通过typeptr来获取解码函数。

2,通过一个特殊的字符NUL来判断字符串的结尾:为了解码,你必须遍历input buffer里面的字符串,但是判断字符串是否到了结尾的过程很慢:

buf : []byte 里面保存了传递给解码器的字符串序列。

cursor : int64 保存了当前解码的位置。

代码语言:javascript复制
buflen := len(buf)
for ; cursor < buflen; cursor   { // compare cursor and buflen at all times, it is so slow.
    switch buf[cursor] {
    case ' ', 'n', 'r', 't':
    }
}

一直比较当前位置和buflen的过程非常低效。因此通过在字符串结尾加一个特殊的字符NUL (00),就可以在检查其它字符的同时检查结尾字符,不必要去做比较操作。

代码语言:javascript复制
for {
    switch buf[cursor] {
    case ' ', 'n', 'r', 't':
    case '00':
        return nil
    }
    cursor  
}

4,使用边界检查消除:go-json是通过指针操作,来实现热点的字符获取和边界检查消除。

5,使用位图来检查结构体的某些字段是否存在:减少结构体解码过程中,寻找字段的耗时。因为在解码过程中字段名的匹配过程很耗时。为了加速这个过程json-iterator/go,针对字段数小于等于10的场景做了如下优化:通过定义hash值和结构体,然后用switch case匹配hash值的过程来加速。因为switch case 的过程比map速度快。但是它有hash值冲突的风险。gojay 使用自己编写的switch case来加速,代替map。go-json采用了一个新的方法bitmap field optimization。每个字符能代表的值的范围可以是[256]byte。如果结构体的字段数目少于8,可以使用int8来代表每个field的状态。具体如下:

代码语言:javascript复制
Base ( 8bit ): 00000000
Key "a": 00000001 ( assign key "a" to the first bit )
Key "b": 00000010 ( assign key "b" to the second bit )
Key "c": 00000100 ( assign key "c" to the third bit )

整个位图的宽度是256,长度是字段名字的最长值。如果字符串的每一个字符都命中了位图,说明它可能是结构体的字段。有个问题,如果字符串是字段的前缀,它也可能命中位图。

下面我们看下如何使用它:

代码语言:javascript复制
package main

import (
  json "github.com/goccy/go-json"

  "fmt"
  "reflect"
)

type T struct {
  X int
  U *U
}

type U struct {
  T *T
}

func main() {

  /*
        type T struct {
        X int
        U *U
      }

      type U struct {
        T *T
      }
         //  undefined: U
  */

  b, err := json.Marshal(&T{
    X: 1,
    U: &U{
      T: &T{
        X: 2,
      },
    },
  })
  fmt.Println(string(b), err) // {"X":1,"U":{"T":{"X":2,"U":null}}}
  c := T{}
  fmt.Println(json.Unmarshal(b, &c), c)
  fmt.Println(reflect.TypeOf(b))
  c1, err := json.MarshalNoEscape(c)
  fmt.Println(string(c1), err)
}

完全兼容标准库的api

代码语言:javascript复制
% go run  ./json/go-json/main.go
{"X":1,"U":{"T":{"X":2,"U":null}}} <nil>
<nil> {1 0xc000012160}
[]uint8
{"X":1,"U":{"T":{"X":2,"U":null}}} <nil>

0 人点赞