github.com/grbit/go-json@v0.11.0/README.md (about)

     1  # Disclaimer
     2  
     3  This fork is created only because I PR review/merge process in the original one was slower. I just wanted to have 
     4  some ubgs fixed, that's it.
     5  
     6  All kudos go to [Goccy](https://github.com/grbit/go-json)
     7  
     8  # go-json
     9  
    10  ![Go](https://github.com/grbit/go-json/workflows/Go/badge.svg)
    11  [![GoDoc](https://godoc.org/github.com/grbit/go-json?status.svg)](https://pkg.go.dev/github.com/grbit/go-json?tab=doc)
    12  [![codecov](https://codecov.io/gh/goccy/go-json/branch/master/graph/badge.svg)](https://codecov.io/gh/goccy/go-json)
    13  
    14  Fast JSON encoder/decoder compatible with encoding/json for Go
    15  
    16  <img width="400px" src="https://user-images.githubusercontent.com/209884/92572337-42b42900-f2bf-11ea-973a-c74a359553a5.png"></img>
    17  
    18  # Roadmap
    19  
    20  ```
    21  * version ( expected release date )
    22  
    23  * v0.9.0
    24   |
    25   | while maintaining compatibility with encoding/json, we will add convenient APIs
    26   |
    27   v
    28  * v1.0.0
    29  ```
    30  
    31  We are accepting requests for features that will be implemented between v0.9.0 and v.1.0.0.
    32  If you have the API you need, please submit your issue [here](https://github.com/grbit/go-json/issues).
    33  
    34  # Features
    35  
    36  - Drop-in replacement of `encoding/json`
    37  - Fast ( See [Benchmark section](https://github.com/grbit/go-json#benchmarks) )
    38  - Flexible customization with options
    39  - Coloring the encoded string
    40  - Can propagate context.Context to `MarshalJSON` or `UnmarshalJSON`
    41  - Can dynamically filter the fields of the structure type-safely
    42  
    43  # Installation
    44  
    45  ```
    46  go get github.com/grbit/go-json
    47  ```
    48  
    49  # How to use
    50  
    51  Replace import statement from `encoding/json` to `github.com/grbit/go-json`
    52  
    53  ```
    54  -import "encoding/json"
    55  +import "github.com/grbit/go-json"
    56  ```
    57  
    58  # JSON library comparison
    59  
    60  |  name  |  encoder | decoder | compatible with `encoding/json` |
    61  | :----: | :------: | :-----: | :-----------------------------: |
    62  | encoding/json |  yes | yes | N/A |
    63  | [json-iterator/go](https://github.com/json-iterator/go) | yes | yes | partial |
    64  | [easyjson](https://github.com/mailru/easyjson) | yes | yes |  no |
    65  | [gojay](https://github.com/francoispqt/gojay) | yes | yes |  no |
    66  | [segmentio/encoding/json](https://github.com/segmentio/encoding/tree/master/json) | yes | yes | partial |
    67  | [jettison](https://github.com/wI2L/jettison) | yes | no | no |
    68  | [simdjson-go](https://github.com/minio/simdjson-go) | no | yes | no |
    69  | goccy/go-json | yes | yes | yes |
    70  
    71  - `json-iterator/go` isn't compatible with `encoding/json` in many ways (e.g. https://github.com/json-iterator/go/issues/229 ), but it hasn't been supported for a long time.
    72  - `segmentio/encoding/json` is well supported for encoders, but some are not supported for decoder APIs such as `Token` ( streaming decode )
    73  
    74  ## Other libraries
    75  
    76  - [jingo](https://github.com/bet365/jingo)
    77  
    78  I tried the benchmark but it didn't work.
    79  Also, it seems to panic when it receives an unexpected value because there is no error handling...
    80  
    81  - [ffjson](https://github.com/pquerna/ffjson)
    82  
    83  Benchmarking gave very slow results.
    84  It seems that it is assumed that the user will use the buffer pool properly.
    85  Also, development seems to have already stopped
    86  
    87  # Benchmarks
    88  
    89  ```
    90  $ cd benchmarks
    91  $ go test -bench .
    92  ```
    93  
    94  ## Encode
    95  
    96  <img width="700px" src="https://user-images.githubusercontent.com/209884/107126758-0845cb00-68f5-11eb-8db7-086fcf9bcfaa.png"></img>
    97  <img width="700px" src="https://user-images.githubusercontent.com/209884/107126757-07ad3480-68f5-11eb-87aa-858cc5eacfcb.png"></img>
    98  
    99  ## Decode
   100  
   101  <img width="700" alt="" src="https://user-images.githubusercontent.com/209884/107979944-bd1d6d80-7002-11eb-944b-9d17b6674e3f.png">
   102  <img width="700" alt="" src="https://user-images.githubusercontent.com/209884/107979931-b989e680-7002-11eb-87a0-66fc22d90dd4.png">
   103  <img width="700" alt="" src="https://user-images.githubusercontent.com/209884/107979940-bc84d700-7002-11eb-9647-869bbc25c9d9.png">
   104  
   105  
   106  # Fuzzing
   107  
   108  [go-json-fuzz](https://github.com/grbit/go-json-fuzz) is the repository for fuzzing tests.
   109  If you run the test in this repository and find a bug, please commit to corpus to go-json-fuzz and report the issue to [go-json](https://github.com/grbit/go-json/issues).
   110  
   111  # How it works
   112  
   113  `go-json` is very fast in both encoding and decoding compared to other libraries.
   114  It's easier to implement by using automatic code generation for performance or by using a dedicated interface, but `go-json` dares to stick to compatibility with `encoding/json` and is the simple interface. Despite this, we are developing with the aim of being the fastest library.
   115  
   116  Here, we explain the various speed-up techniques implemented by `go-json`.
   117  
   118  ## Basic technique
   119  
   120  The techniques listed here are the ones used by most of the libraries listed above.
   121  
   122  ### Buffer reuse
   123  
   124  Since the only value required for the result of `json.Marshal(interface{}) ([]byte, error)` is `[]byte`, the only value that must be allocated during encoding is the return value `[]byte` .
   125  
   126  Also, as the number of allocations increases, the performance will be affected, so the number of allocations should be kept as low as possible when creating `[]byte`.
   127  
   128  Therefore, there is a technique to reduce the number of times a new buffer must be allocated by reusing the buffer used for the previous encoding by using `sync.Pool`.
   129  
   130  Finally, you allocate a buffer that is as long as the resulting buffer and copy the contents into it, you only need to allocate the buffer once in theory.
   131  
   132  ```go
   133  type buffer struct {
   134      data []byte
   135  }
   136  
   137  var bufPool = sync.Pool{
   138      New: func() interface{} {
   139          return &buffer{data: make([]byte, 0, 1024)}
   140      },
   141  }
   142  
   143  buf := bufPool.Get().(*buffer)
   144  data := encode(buf.data) // reuse buf.data
   145  
   146  newBuf := make([]byte, len(data))
   147  copy(newBuf, buf)
   148  
   149  buf.data = data
   150  bufPool.Put(buf)
   151  ```
   152  
   153  ### Elimination of reflection
   154  
   155  As you know, the reflection operation is very slow.
   156  
   157  Therefore, using the fact that the address position where the type information is stored is fixed for each binary ( we call this `typeptr` ),
   158  we can use the address in the type information to call a pre-built optimized process.
   159  
   160  For example, you can get the address to the type information from `interface{}` as follows and you can use that information to call a process that does not have reflection.
   161  
   162  To process without reflection, pass a pointer (`unsafe.Pointer`) to the value is stored.
   163  
   164  ```go
   165  
   166  type emptyInterface struct {
   167      typ unsafe.Pointer
   168      ptr unsafe.Pointer
   169  }
   170  
   171  var typeToEncoder = map[uintptr]func(unsafe.Pointer)([]byte, error){}
   172  
   173  func Marshal(v interface{}) ([]byte, error) {
   174      iface := (*emptyInterface)(unsafe.Pointer(&v)
   175      typeptr := uintptr(iface.typ)
   176      if enc, exists := typeToEncoder[typeptr]; exists {
   177          return enc(iface.ptr)
   178      }
   179      ...
   180  }
   181  ```
   182  
   183  ※ In reality, `typeToEncoder` can be referenced by multiple goroutines, so exclusive control is required.
   184  
   185  ## Unique speed-up technique
   186  
   187  ## Encoder
   188  
   189  ### Do not escape arguments of `Marshal`
   190  
   191  `json.Marshal` and `json.Unmarshal` receive `interface{}` value and they perform type determination dynamically to process.
   192  In normal case, you need to use the `reflect` library to determine the type dynamically, but since `reflect.Type` is defined as `interface`, when you call the method of `reflect.Type`, The reflect's argument is escaped.
   193  
   194  Therefore, the arguments for `Marshal` and `Unmarshal` are always escaped to the heap.
   195  However, `go-json` can use the feature of `reflect.Type` while avoiding escaping.
   196  
   197  `reflect.Type` is defined as `interface`, but in reality `reflect.Type` is implemented only by the structure `rtype` defined in the `reflect` package.
   198  For this reason, to date `reflect.Type` is the same as `*reflect.rtype`.
   199  
   200  Therefore, by directly handling `*reflect.rtype`, which is an implementation of `reflect.Type`, it is possible to avoid escaping because it changes from `interface` to using `struct`.
   201  
   202  The technique for working with `*reflect.rtype` directly from `go-json` is implemented at [rtype.go](https://github.com/grbit/go-json/blob/master/internal/runtime/rtype.go)
   203  
   204  Also, the same technique is cut out as a library ( https://github.com/grbit/go-reflect )
   205  
   206  Initially this feature was the default behavior of `go-json`.
   207  But after careful testing, I found that I passed a large value to `json.Marshal()` and if the argument could not be assigned to the stack, it could not be properly escaped to the heap (a bug in the Go compiler).
   208  
   209  Therefore, this feature will be provided as an **optional** until this issue is resolved.
   210  
   211  To use it, add `NoEscape` like `MarshalNoEscape()`
   212  
   213  ### Encoding using opcode sequence
   214  
   215  I explained that you can use `typeptr` to call a pre-built process from type information.
   216  
   217  In other libraries, this dedicated process is processed by making it an function calling like anonymous function, but function calls are inherently slow processes and should be avoided as much as possible.
   218  
   219  Therefore, `go-json` adopted the Instruction-based execution processing system, which is also used to implement virtual machines for programming language.
   220  
   221  If it is the first type to encode, create the opcode ( instruction ) sequence required for encoding.
   222  From the second time onward, use `typeptr` to get the cached pre-built opcode sequence and encode it based on it. An example of the opcode sequence is shown below.
   223  
   224  ```go
   225  json.Marshal(struct{
   226      X int `json:"x"`
   227      Y string `json:"y"`
   228  }{X: 1, Y: "hello"})
   229  ```
   230  
   231  When encoding a structure like the one above, create a sequence of opcodes like this:
   232  
   233  ```
   234  - opStructFieldHead ( `{` )
   235  - opStructFieldInt ( `"x": 1,` )
   236  - opStructFieldString ( `"y": "hello"` )
   237  - opStructEnd ( `}` )
   238  - opEnd
   239  ```
   240  
   241  ※ When processing each operation, write the letters on the right.
   242  
   243  In addition, each opcode is managed by the following structure ( 
   244  Pseudo code ).
   245  
   246  ```go
   247  type opType int
   248  const (
   249      opStructFieldHead opType = iota
   250      opStructFieldInt
   251      opStructFieldStirng
   252      opStructEnd
   253      opEnd
   254  )
   255  type opcode struct {
   256      op opType
   257      key []byte
   258      next *opcode
   259  }
   260  ```
   261  
   262  The process of encoding using the opcode sequence is roughly implemented as follows.
   263  
   264  ```go
   265  func encode(code *opcode, b []byte, p unsafe.Pointer) ([]byte, error) {
   266      for {
   267          switch code.op {
   268          case opStructFieldHead:
   269              b = append(b, '{')
   270              code = code.next
   271          case opStructFieldInt:
   272              b = append(b, code.key...)
   273              b = appendInt((*int)(unsafe.Pointer(uintptr(p)+code.offset)))
   274              code = code.next
   275          case opStructFieldString:
   276              b = append(b, code.key...)
   277              b = appendString((*string)(unsafe.Pointer(uintptr(p)+code.offset)))
   278              code = code.next
   279          case opStructEnd:
   280              b = append(b, '}')
   281              code = code.next
   282          case opEnd:
   283              goto END
   284          }
   285      }
   286  END:
   287      return b, nil
   288  }
   289  ```
   290  
   291  In this way, the huge `switch-case` is used to encode by manipulating the linked list opcodes to avoid unnecessary function calls.
   292  
   293  ### Opcode sequence optimization
   294  
   295  One of the advantages of encoding using the opcode sequence is the ease of optimization.
   296  The opcode sequence mentioned above is actually converted into the following optimized operations and used.
   297  
   298  ```
   299  - opStructFieldHeadInt ( `{"x": 1,` )
   300  - opStructEndString ( `"y": "hello"}` )
   301  - opEnd
   302  ```
   303  
   304  It has been reduced from 5 opcodes to 3 opcodes !
   305  Reducing the number of opcodees means reducing the number of branches with `switch-case`.
   306  In other words, the closer the number of operations is to 1, the faster the processing can be performed.
   307  
   308  In `go-json`, optimization to reduce the number of opcodes itself like the above and it speeds up by preparing opcodes with optimized paths.
   309  
   310  ### Change recursive call from CALL to JMP
   311  
   312  Recursive processing is required during encoding if the type is defined recursively as follows:
   313  
   314  ```go
   315  type T struct {
   316      X int
   317      U *U
   318  }
   319  
   320  type U struct {
   321      T *T
   322  }
   323  
   324  b, err := json.Marshal(&T{
   325      X: 1,
   326      U: &U{
   327          T: &T{
   328              X: 2,
   329          },
   330      },
   331  })
   332  fmt.Println(string(b)) // {"X":1,"U":{"T":{"X":2,"U":null}}}
   333  ```
   334  
   335  In `go-json`, recursive processing is processed by the operation type of ` opStructFieldRecursive`.
   336  
   337  In this operation, after acquiring the opcode sequence used for recursive processing, the function is **not** called recursively as it is, but the necessary values ​​are saved by itself and implemented by moving to the next operation.
   338  
   339  The technique of implementing recursive processing with the `JMP` operation while avoiding the `CALL` operation is a famous technique for implementing a high-speed virtual machine.
   340  
   341  For more details, please refer to [the article](https://engineering.mercari.com/blog/entry/1599563768-081104c850) ( but Japanese only ).
   342  
   343  ### Dispatch by typeptr from map to slice
   344  
   345  When retrieving the data cached from the type information by `typeptr`, we usually use map.
   346  Map requires exclusive control, so use `sync.Map` for a naive implementation.
   347  
   348  However, this is slow, so it's a good idea to use the `atomic` package for exclusive control as implemented by `segmentio/encoding/json` ( https://github.com/segmentio/encoding/blob/master/json/codec.go#L41-L55 ).
   349  
   350  This implementation slows down the set instead of speeding up the get, but it works well because of the nature of the library, it encodes much more for the same type.
   351  
   352  However, as a result of profiling, I noticed that `runtime.mapaccess2` accounts for a significant percentage of the execution time. So I thought if I could change the lookup from map to slice.
   353  
   354  There is an API named `typelinks` defined in the `runtime` package that the `reflect` package uses internally.
   355  This allows you to get all the type information defined in the binary at runtime.
   356  
   357  The fact that all type information can be acquired means that by constructing slices in advance with the acquired total number of type information, it is possible to look up with the value of `typeptr` without worrying about out-of-range access.
   358  
   359  However, if there is too much type information, it will use a lot of memory, so by default we will only use this optimization if the slice size fits within **2Mib** .
   360  
   361  If this approach is not available, it will fall back to the `atomic` based process described above.
   362  
   363  If you want to know more, please refer to the implementation [here](https://github.com/grbit/go-json/blob/master/internal/runtime/type.go#L36-L100)
   364  
   365  ## Decoder
   366  
   367  ### Dispatch by typeptr from map to slice
   368  
   369  Like the encoder, the decoder also uses typeptr to call the dedicated process.
   370  
   371  ### Faster termination character inspection using NUL character
   372  
   373  In order to decode, you have to traverse the input buffer character by position.
   374  At that time, if you check whether the buffer has reached the end, it will be very slow.
   375  
   376  `buf` : `[]byte` type variable. holds the string passed to the decoder
   377  `cursor` : `int64` type variable. holds the current read position
   378  
   379  ```go
   380  buflen := len(buf)
   381  for ; cursor < buflen; cursor++ { // compare cursor and buflen at all times, it is so slow.
   382      switch buf[cursor] {
   383      case ' ', '\n', '\r', '\t':
   384      }
   385  }
   386  ```
   387  
   388  Therefore, by adding the `NUL` (`\000`) character to the end of the read buffer as shown below, it is possible to check the termination character at the same time as other characters.
   389  
   390  ```go
   391  for {
   392      switch buf[cursor] {
   393      case ' ', '\n', '\r', '\t':
   394      case '\000':
   395          return nil
   396      }
   397      cursor++
   398  }
   399  ```
   400  
   401  ### Use Boundary Check Elimination
   402  
   403  Due to the `NUL` character optimization, the Go compiler does a boundary check every time, even though `buf[cursor]` does not cause out-of-range access.
   404  
   405  Therefore, `go-json` eliminates boundary check by fetching characters for hotspot by pointer operation. For example, the following code.
   406  
   407  ```go
   408  func char(ptr unsafe.Pointer, offset int64) byte {
   409  	return *(*byte)(unsafe.Pointer(uintptr(ptr) + uintptr(offset)))
   410  }
   411  
   412  p := (*sliceHeader)(&unsafe.Pointer(buf)).data
   413  for {
   414      switch char(p, cursor) {
   415      case ' ', '\n', '\r', '\t':
   416      case '\000':
   417          return nil
   418      }
   419      cursor++
   420  }
   421  ```
   422  
   423  ### Checking the existence of fields of struct using Bitmaps
   424  
   425  I found by the profiling result, in the struct decode, lookup process for field was taking a long time.
   426  
   427  For example, consider decoding a string like `{"a":1,"b":2,"c":3}` into the following structure:
   428  
   429  ```go
   430  type T struct {
   431      A int `json:"a"`
   432      B int `json:"b"`
   433      C int `json:"c"`
   434  }
   435  ```
   436  
   437  At this time, it was found that it takes a lot of time to acquire the decoding process corresponding to the field from the field name as shown below during the decoding process.
   438  
   439  ```go
   440  fieldName := decodeKey(buf, cursor) // "a" or "b" or "c"
   441  decoder, exists := fieldToDecoderMap[fieldName] // so slow
   442  if exists {
   443      decoder(buf, cursor)
   444  } else {
   445      skipValue(buf, cursor)
   446  }
   447  ```
   448  
   449  To improve this process, `json-iterator/go` is optimized so that it can be branched by switch-case when the number of fields in the structure is 10 or less (switch-case is faster than map). However, there is a risk of hash collision because the value hashed by the FNV algorithm is used for conditional branching. Also, `gojay` processes this part at high speed by letting the library user yourself write `switch-case`.
   450  
   451  
   452  `go-json` considers and implements a new approach that is different from these. I call this **bitmap field optimization**.
   453  
   454  The range of values ​​per character can be represented by `[256]byte`. Also, if the number of fields in the structure is 8 or less, `int8` type can represent the state of each field.
   455  In other words, it has the following structure.
   456  
   457  - Base ( 8bit ): `00000000`
   458  - Key "a": `00000001` ( assign key "a" to the first bit )
   459  - Key "b": `00000010` ( assign key "b" to the second bit )
   460  - Key "c": `00000100` ( assign key "c" to the third bit )
   461  
   462  Bitmap structure is the following
   463  
   464  ```
   465          | key index(0) |
   466  ------------------------
   467   0      | 00000000     |
   468   1      | 00000000     |
   469  ~~      |              |
   470  97 (a)  | 00000001     |
   471  98 (b)  | 00000010     |
   472  99 (c)  | 00000100     |
   473  ~~      |              |
   474  255     | 00000000     |
   475  ```
   476  
   477  You can think of this as a Bitmap with a height of `256` and a width of the maximum string length in the field name.
   478  In other words, it can be represented by the following type .
   479  
   480  ```go
   481  [maxFieldKeyLength][256]int8
   482  ```
   483  
   484  When decoding a field character, check whether the corresponding character exists by referring to the pre-built bitmap like the following.
   485  
   486  ```go
   487  var curBit int8 = math.MaxInt8 // 11111111
   488  
   489  c := char(buf, cursor)
   490  bit := bitmap[keyIdx][c]
   491  curBit &= bit
   492  if curBit == 0 {
   493      // not found field
   494  }
   495  ```
   496  
   497  If `curBit` is not `0` until the end of the field string, then the string is
   498  You may have hit one of the fields.
   499  But the possibility is that if the decoded string is shorter than the field string, you will get a false hit.
   500  
   501  - input: `{"a":1}`
   502  ```go
   503  type T struct {
   504      X int `json:"abc"`
   505  }
   506  ```
   507  ※ Since `a` is shorter than `abc`, it can decode to the end of the field character without `curBit` being 0.
   508  
   509  Rest assured. In this case, it doesn't matter because you can tell if you hit by comparing the string length of `a` with the string length of `abc`.
   510  
   511  Finally, calculate the position of the bit where `1` is set and get the corresponding value, and you're done.
   512  
   513  Using this technique, field lookups are possible with only bitwise operations and access to slices.
   514  
   515  `go-json` uses a similar technique for fields with 9 or more and 16 or less fields. At this time, Bitmap is constructed as `[maxKeyLen][256]int16` type.
   516  
   517  Currently, this optimization is not performed when the maximum length of the field name is long (specifically, 64 bytes or more) in addition to the limitation of the number of fields from the viewpoint of saving memory usage.
   518  
   519  ### Others
   520  
   521  I have done a lot of other optimizations. I will find time to write about them. If you have any questions about what's written here or other optimizations, please visit the `#go-json` channel on `gophers.slack.com` .
   522  
   523  ## Reference
   524  
   525  Regarding the story of go-json, there are the following articles in Japanese only.
   526  
   527  - https://speakerdeck.com/goccy/zui-su-falsejsonraiburariwoqiu-mete
   528  - https://engineering.mercari.com/blog/entry/1599563768-081104c850/
   529  
   530  # Looking for Sponsors
   531  
   532  I'm looking for sponsors this library. This library is being developed as a personal project in my spare time. If you want a quick response or problem resolution when using this library in your project, please register as a [sponsor](https://github.com/sponsors/goccy). I will cooperate as much as possible. Of course, this library is developed as an MIT license, so you can use it freely for free.
   533  
   534  # License
   535  
   536  MIT