git.lukeshu.com/go/lowmemjson@v0.3.9-0.20230723050957-72f6d13f6fb2/compat/json/borrowed_bench_test.go (about) 1 // Copyright 2011 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 // 5 // SPDX-License-Identifier: BSD-3-Clause 6 7 // Large data benchmark. 8 // The JSON data is a summary of agl's changes in the 9 // go, webkit, and chromium open source projects. 10 // We benchmark converting between the JSON form 11 // and in-memory data structures. 12 13 package json 14 15 import ( 16 "bytes" 17 "compress/gzip" 18 "fmt" 19 "io" 20 "os" 21 "reflect" 22 "regexp" 23 "runtime" 24 "strings" 25 "sync" 26 "testing" 27 ) 28 29 type codeResponse struct { 30 Tree *codeNode `json:"tree"` 31 Username string `json:"username"` 32 } 33 34 type codeNode struct { 35 Name string `json:"name"` 36 Kids []*codeNode `json:"kids"` 37 CLWeight float64 `json:"cl_weight"` 38 Touches int `json:"touches"` 39 MinT int64 `json:"min_t"` 40 MaxT int64 `json:"max_t"` 41 MeanT int64 `json:"mean_t"` 42 } 43 44 var codeJSON []byte 45 var codeStruct codeResponse 46 47 func codeInit(b *testing.B) { // MODIFIED: use the test logger 48 f, err := os.Open("testdata/code.json.gz") 49 if err != nil { 50 panic(err) 51 } 52 defer f.Close() 53 gz, err := gzip.NewReader(f) 54 if err != nil { 55 panic(err) 56 } 57 data, err := io.ReadAll(gz) 58 if err != nil { 59 panic(err) 60 } 61 62 codeJSON = data 63 64 if err := Unmarshal(codeJSON, &codeStruct); err != nil { 65 panic("unmarshal code.json: " + err.Error()) 66 } 67 68 if data, err = Marshal(&codeStruct); err != nil { 69 panic("marshal code.json: " + err.Error()) 70 } 71 72 if !bytes.Equal(data, codeJSON) { 73 b.Log("different lengths", len(data), len(codeJSON)) // MODIFIED: use the test logger 74 for i := 0; i < len(data) && i < len(codeJSON); i++ { 75 if data[i] != codeJSON[i] { 76 b.Log("re-marshal: changed at byte", i) // MODIFIED: use the test logger 77 b.Log("orig: ", string(codeJSON[i-10:i+10])) // MODIFIED: use the test logger 78 b.Log("new: ", string(data[i-10:i+10])) // MODIFIED: use the test logger 79 break 80 } 81 } 82 panic("re-marshal code.json: different result") 83 } 84 } 85 86 func BenchmarkCodeEncoder(b *testing.B) { 87 b.ReportAllocs() 88 if codeJSON == nil { 89 b.StopTimer() 90 codeInit(b) // MODIFIED: use the test logger 91 b.StartTimer() 92 } 93 b.RunParallel(func(pb *testing.PB) { 94 enc := NewEncoder(io.Discard) 95 for pb.Next() { 96 if err := enc.Encode(&codeStruct); err != nil { 97 b.Fatal("Encode:", err) 98 } 99 } 100 }) 101 b.SetBytes(int64(len(codeJSON))) 102 } 103 104 func BenchmarkCodeEncoderError(b *testing.B) { 105 b.ReportAllocs() 106 if codeJSON == nil { 107 b.StopTimer() 108 codeInit(b) // MODIFIED: use the test logger 109 b.StartTimer() 110 } 111 112 // Trigger an error in Marshal with cyclic data. 113 type Dummy struct { 114 Name string 115 Next *Dummy 116 } 117 dummy := Dummy{Name: "Dummy"} 118 dummy.Next = &dummy 119 120 b.RunParallel(func(pb *testing.PB) { 121 enc := NewEncoder(io.Discard) 122 for pb.Next() { 123 if err := enc.Encode(&codeStruct); err != nil { 124 b.Fatal("Encode:", err) 125 } 126 if _, err := Marshal(dummy); err == nil { 127 b.Fatal("expect an error here") 128 } 129 } 130 }) 131 b.SetBytes(int64(len(codeJSON))) 132 } 133 134 func BenchmarkCodeMarshal(b *testing.B) { 135 b.ReportAllocs() 136 if codeJSON == nil { 137 b.StopTimer() 138 codeInit(b) // MODIFIED: use the test logger 139 b.StartTimer() 140 } 141 b.RunParallel(func(pb *testing.PB) { 142 for pb.Next() { 143 if _, err := Marshal(&codeStruct); err != nil { 144 b.Fatal("Marshal:", err) 145 } 146 } 147 }) 148 b.SetBytes(int64(len(codeJSON))) 149 } 150 151 func BenchmarkCodeMarshalError(b *testing.B) { 152 b.ReportAllocs() 153 if codeJSON == nil { 154 b.StopTimer() 155 codeInit(b) // MODIFIED: use the test logger 156 b.StartTimer() 157 } 158 159 // Trigger an error in Marshal with cyclic data. 160 type Dummy struct { 161 Name string 162 Next *Dummy 163 } 164 dummy := Dummy{Name: "Dummy"} 165 dummy.Next = &dummy 166 167 b.RunParallel(func(pb *testing.PB) { 168 for pb.Next() { 169 if _, err := Marshal(&codeStruct); err != nil { 170 b.Fatal("Marshal:", err) 171 } 172 if _, err := Marshal(dummy); err == nil { 173 b.Fatal("expect an error here") 174 } 175 } 176 }) 177 b.SetBytes(int64(len(codeJSON))) 178 } 179 180 func benchMarshalBytes(n int) func(*testing.B) { 181 sample := []byte("hello world") 182 // Use a struct pointer, to avoid an allocation when passing it as an 183 // interface parameter to Marshal. 184 v := &struct { 185 Bytes []byte 186 }{ 187 bytes.Repeat(sample, (n/len(sample))+1)[:n], 188 } 189 return func(b *testing.B) { 190 for i := 0; i < b.N; i++ { 191 if _, err := Marshal(v); err != nil { 192 b.Fatal("Marshal:", err) 193 } 194 } 195 } 196 } 197 198 func benchMarshalBytesError(n int) func(*testing.B) { 199 sample := []byte("hello world") 200 // Use a struct pointer, to avoid an allocation when passing it as an 201 // interface parameter to Marshal. 202 v := &struct { 203 Bytes []byte 204 }{ 205 bytes.Repeat(sample, (n/len(sample))+1)[:n], 206 } 207 208 // Trigger an error in Marshal with cyclic data. 209 type Dummy struct { 210 Name string 211 Next *Dummy 212 } 213 dummy := Dummy{Name: "Dummy"} 214 dummy.Next = &dummy 215 216 return func(b *testing.B) { 217 for i := 0; i < b.N; i++ { 218 if _, err := Marshal(v); err != nil { 219 b.Fatal("Marshal:", err) 220 } 221 if _, err := Marshal(dummy); err == nil { 222 b.Fatal("expect an error here") 223 } 224 } 225 } 226 } 227 228 func BenchmarkMarshalBytes(b *testing.B) { 229 b.ReportAllocs() 230 // 32 fits within encodeState.scratch. 231 b.Run("32", benchMarshalBytes(32)) 232 // 256 doesn't fit in encodeState.scratch, but is small enough to 233 // allocate and avoid the slower base64.NewEncoder. 234 b.Run("256", benchMarshalBytes(256)) 235 // 4096 is large enough that we want to avoid allocating for it. 236 b.Run("4096", benchMarshalBytes(4096)) 237 } 238 239 func BenchmarkMarshalBytesError(b *testing.B) { 240 b.ReportAllocs() 241 // 32 fits within encodeState.scratch. 242 b.Run("32", benchMarshalBytesError(32)) 243 // 256 doesn't fit in encodeState.scratch, but is small enough to 244 // allocate and avoid the slower base64.NewEncoder. 245 b.Run("256", benchMarshalBytesError(256)) 246 // 4096 is large enough that we want to avoid allocating for it. 247 b.Run("4096", benchMarshalBytesError(4096)) 248 } 249 250 func BenchmarkCodeDecoder(b *testing.B) { 251 b.ReportAllocs() 252 if codeJSON == nil { 253 b.StopTimer() 254 codeInit(b) // MODIFIED: use the test logger 255 b.StartTimer() 256 } 257 b.RunParallel(func(pb *testing.PB) { 258 var buf bytes.Buffer 259 dec := NewDecoder(&buf) 260 var r codeResponse 261 for pb.Next() { 262 buf.Write(codeJSON) 263 // hide EOF 264 buf.WriteByte('\n') 265 buf.WriteByte('\n') 266 buf.WriteByte('\n') 267 if err := dec.Decode(&r); err != nil { 268 b.Fatal("Decode:", err) 269 } 270 } 271 }) 272 b.SetBytes(int64(len(codeJSON))) 273 } 274 275 func BenchmarkUnicodeDecoder(b *testing.B) { 276 b.ReportAllocs() 277 j := []byte(`"\uD83D\uDE01"`) 278 b.SetBytes(int64(len(j))) 279 r := bytes.NewReader(j) 280 dec := NewDecoder(r) 281 var out string 282 b.ResetTimer() 283 for i := 0; i < b.N; i++ { 284 if err := dec.Decode(&out); err != nil { 285 b.Fatal("Decode:", err) 286 } 287 if _, err := r.Seek(0, 0); err != nil { // MODIFIED: check the error 288 b.Fatal("Seek:", err) // MODIFIED: added 289 } // MODIFIED: added 290 } 291 } 292 293 func BenchmarkDecoderStream(b *testing.B) { 294 b.ReportAllocs() 295 b.StopTimer() 296 var buf bytes.Buffer 297 dec := NewDecoder(&buf) 298 buf.WriteString(`"` + strings.Repeat("x", 1000000) + `"` + "\n\n\n") 299 var x any 300 if err := dec.Decode(&x); err != nil { 301 b.Fatal("Decode:", err) 302 } 303 ones := strings.Repeat(" 1\n", 300000) + "\n\n\n" 304 b.StartTimer() 305 for i := 0; i < b.N; i++ { 306 if i%300000 == 0 { 307 buf.WriteString(ones) 308 } 309 x = nil 310 if err := dec.Decode(&x); err != nil || x != 1.0 { 311 b.Fatalf("Decode: %v after %d", err, i) 312 } 313 } 314 } 315 316 func BenchmarkCodeUnmarshal(b *testing.B) { 317 b.ReportAllocs() 318 if codeJSON == nil { 319 b.StopTimer() 320 codeInit(b) // MODIFIED: use the test logger 321 b.StartTimer() 322 } 323 b.RunParallel(func(pb *testing.PB) { 324 for pb.Next() { 325 var r codeResponse 326 if err := Unmarshal(codeJSON, &r); err != nil { 327 b.Fatal("Unmarshal:", err) 328 } 329 } 330 }) 331 b.SetBytes(int64(len(codeJSON))) 332 } 333 334 func BenchmarkCodeUnmarshalReuse(b *testing.B) { 335 b.ReportAllocs() 336 if codeJSON == nil { 337 b.StopTimer() 338 codeInit(b) // MODIFIED: use the test logger 339 b.StartTimer() 340 } 341 b.RunParallel(func(pb *testing.PB) { 342 var r codeResponse 343 for pb.Next() { 344 if err := Unmarshal(codeJSON, &r); err != nil { 345 b.Fatal("Unmarshal:", err) 346 } 347 } 348 }) 349 b.SetBytes(int64(len(codeJSON))) 350 } 351 352 func BenchmarkUnmarshalString(b *testing.B) { 353 b.ReportAllocs() 354 data := []byte(`"hello, world"`) 355 b.RunParallel(func(pb *testing.PB) { 356 var s string 357 for pb.Next() { 358 if err := Unmarshal(data, &s); err != nil { 359 b.Fatal("Unmarshal:", err) 360 } 361 } 362 }) 363 } 364 365 func BenchmarkUnmarshalFloat64(b *testing.B) { 366 b.ReportAllocs() 367 data := []byte(`3.14`) 368 b.RunParallel(func(pb *testing.PB) { 369 var f float64 370 for pb.Next() { 371 if err := Unmarshal(data, &f); err != nil { 372 b.Fatal("Unmarshal:", err) 373 } 374 } 375 }) 376 } 377 378 func BenchmarkUnmarshalInt64(b *testing.B) { 379 b.ReportAllocs() 380 data := []byte(`3`) 381 b.RunParallel(func(pb *testing.PB) { 382 var x int64 383 for pb.Next() { 384 if err := Unmarshal(data, &x); err != nil { 385 b.Fatal("Unmarshal:", err) 386 } 387 } 388 }) 389 } 390 391 func BenchmarkIssue10335(b *testing.B) { 392 b.ReportAllocs() 393 j := []byte(`{"a":{ }}`) 394 b.RunParallel(func(pb *testing.PB) { 395 var s struct{} 396 for pb.Next() { 397 if err := Unmarshal(j, &s); err != nil { 398 b.Fatal(err) 399 } 400 } 401 }) 402 } 403 404 func BenchmarkIssue34127(b *testing.B) { 405 b.ReportAllocs() 406 j := struct { 407 Bar string `json:"bar,string"` 408 }{ 409 Bar: `foobar`, 410 } 411 b.RunParallel(func(pb *testing.PB) { 412 for pb.Next() { 413 if _, err := Marshal(&j); err != nil { 414 b.Fatal(err) 415 } 416 } 417 }) 418 } 419 420 func BenchmarkUnmapped(b *testing.B) { 421 b.ReportAllocs() 422 j := []byte(`{"s": "hello", "y": 2, "o": {"x": 0}, "a": [1, 99, {"x": 1}]}`) 423 b.RunParallel(func(pb *testing.PB) { 424 var s struct{} 425 for pb.Next() { 426 if err := Unmarshal(j, &s); err != nil { 427 b.Fatal(err) 428 } 429 } 430 }) 431 } 432 433 func BenchmarkTypeFieldsCache(b *testing.B) { 434 b.ReportAllocs() 435 var maxTypes int = 1e6 436 if false { // testenv.Builder() != "" { // MODIFIED: disabled 437 maxTypes = 1e3 // restrict cache sizes on builders 438 } 439 440 // Dynamically generate many new types. 441 types := make([]reflect.Type, maxTypes) 442 fs := []reflect.StructField{{ 443 Type: reflect.TypeOf(""), 444 Index: []int{0}, 445 }} 446 for i := range types { 447 fs[0].Name = fmt.Sprintf("TypeFieldsCache%d", i) 448 types[i] = reflect.StructOf(fs) 449 } 450 451 // clearClear clears the cache. Other JSON operations, must not be running. 452 // clearCache := func() { // MODIFIED: use function from compat_test.go 453 // fieldCache = sync.Map{} // MODIFIED: use function from compat_test.go 454 // } // MODIFIED: use function from compat_test.go 455 456 // MissTypes tests the performance of repeated cache misses. 457 // This measures the time to rebuild a cache of size nt. 458 for nt := 1; nt <= maxTypes; nt *= 10 { 459 ts := types[:nt] 460 b.Run(fmt.Sprintf("MissTypes%d", nt), func(b *testing.B) { 461 nc := runtime.GOMAXPROCS(0) 462 for i := 0; i < b.N; i++ { 463 clearCache() 464 var wg sync.WaitGroup 465 for j := 0; j < nc; j++ { 466 wg.Add(1) 467 go func(j int) { 468 for _, t := range ts[(j*len(ts))/nc : ((j+1)*len(ts))/nc] { 469 cachedTypeFields(t) 470 } 471 wg.Done() 472 }(j) 473 } 474 wg.Wait() 475 } 476 }) 477 } 478 479 // HitTypes tests the performance of repeated cache hits. 480 // This measures the average time of each cache lookup. 481 for nt := 1; nt <= maxTypes; nt *= 10 { 482 // Pre-warm a cache of size nt. 483 clearCache() 484 for _, t := range types[:nt] { 485 cachedTypeFields(t) 486 } 487 b.Run(fmt.Sprintf("HitTypes%d", nt), func(b *testing.B) { 488 b.RunParallel(func(pb *testing.PB) { 489 for pb.Next() { 490 cachedTypeFields(types[0]) 491 } 492 }) 493 }) 494 } 495 } 496 497 func BenchmarkEncodeMarshaler(b *testing.B) { 498 b.ReportAllocs() 499 500 m := struct { 501 A int 502 B RawMessage 503 }{} 504 505 b.RunParallel(func(pb *testing.PB) { 506 enc := NewEncoder(io.Discard) 507 508 for pb.Next() { 509 if err := enc.Encode(&m); err != nil { 510 b.Fatal("Encode:", err) 511 } 512 } 513 }) 514 } 515 516 func BenchmarkEncoderEncode(b *testing.B) { 517 b.ReportAllocs() 518 type T struct { 519 X, Y string 520 } 521 v := &T{"foo", "bar"} 522 b.RunParallel(func(pb *testing.PB) { 523 for pb.Next() { 524 if err := NewEncoder(io.Discard).Encode(v); err != nil { 525 b.Fatal(err) 526 } 527 } 528 }) 529 } 530 531 func BenchmarkNumberIsValid(b *testing.B) { 532 s := "-61657.61667E+61673" 533 for i := 0; i < b.N; i++ { 534 isValidNumber(s) 535 } 536 } 537 538 func BenchmarkNumberIsValidRegexp(b *testing.B) { 539 var jsonNumberRegexp = regexp.MustCompile(`^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$`) 540 s := "-61657.61667E+61673" 541 for i := 0; i < b.N; i++ { 542 jsonNumberRegexp.MatchString(s) 543 } 544 }