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