gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

README.md (20262B)


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