github.com/neilotoole/jsoncolor@v0.7.2-0.20231115150201-1637fae69be1/golang_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 // Large data benchmark. 6 // The JSON data is a summary of agl's changes in the 7 // go, webkit, and chromium open source projects. 8 // We benchmark converting between the JSON form 9 // and in-memory data structures. 10 11 package jsoncolor 12 13 import ( 14 "bytes" 15 "compress/gzip" 16 "fmt" 17 "io/ioutil" 18 "os" 19 "reflect" 20 "runtime" 21 "strings" 22 "sync" 23 "testing" 24 ) 25 26 type codeResponse struct { 27 Tree *codeNode `json:"tree"` 28 Username string `json:"username"` 29 } 30 31 type codeNode struct { 32 Name string `json:"name"` 33 Kids []*codeNode `json:"kids"` 34 CLWeight float64 `json:"cl_weight"` 35 Touches int `json:"touches"` 36 MinT int64 `json:"min_t"` 37 MaxT int64 `json:"max_t"` 38 MeanT int64 `json:"mean_t"` 39 } 40 41 var ( 42 codeJSON []byte 43 codeStruct codeResponse 44 ) 45 46 func codeInit() { 47 f, err := os.Open("testdata/code.json.gz") 48 if err != nil { 49 panic(err) 50 } 51 defer f.Close() 52 gz, err := gzip.NewReader(f) 53 if err != nil { 54 panic(err) 55 } 56 data, err := ioutil.ReadAll(gz) 57 if err != nil { 58 panic(err) 59 } 60 61 codeJSON = data 62 63 if err := Unmarshal(codeJSON, &codeStruct); err != nil { 64 panic("unmarshal code.json: " + err.Error()) 65 } 66 67 if data, err = Marshal(&codeStruct); err != nil { 68 panic("marshal code.json: " + err.Error()) 69 } 70 71 if !bytes.Equal(data, codeJSON) { 72 println("different lengths", len(data), len(codeJSON)) 73 for i := 0; i < len(data) && i < len(codeJSON); i++ { 74 if data[i] != codeJSON[i] { 75 println("re-marshal: changed at byte", i) 76 println("orig: ", string(codeJSON[i-10:i+10])) 77 println("new: ", string(data[i-10:i+10])) 78 break 79 } 80 } 81 panic("re-marshal code.json: different result") 82 } 83 } 84 85 func BenchmarkCodeEncoder(b *testing.B) { 86 b.ReportAllocs() 87 if codeJSON == nil { 88 b.StopTimer() 89 codeInit() 90 b.StartTimer() 91 } 92 b.RunParallel(func(pb *testing.PB) { 93 enc := NewEncoder(ioutil.Discard) 94 for pb.Next() { 95 if err := enc.Encode(&codeStruct); err != nil { 96 b.Fatal("Encode:", err) 97 } 98 } 99 }) 100 b.SetBytes(int64(len(codeJSON))) 101 } 102 103 func BenchmarkCodeMarshal(b *testing.B) { 104 b.ReportAllocs() 105 if codeJSON == nil { 106 b.StopTimer() 107 codeInit() 108 b.StartTimer() 109 } 110 b.RunParallel(func(pb *testing.PB) { 111 for pb.Next() { 112 if _, err := Marshal(&codeStruct); err != nil { 113 b.Fatal("Marshal:", err) 114 } 115 } 116 }) 117 b.SetBytes(int64(len(codeJSON))) 118 } 119 120 func benchMarshalBytes(n int) func(*testing.B) { 121 sample := []byte("hello world") 122 // Use a struct pointer, to avoid an allocation when passing it as an 123 // interface parameter to Marshal. 124 v := &struct { 125 Bytes []byte 126 }{ 127 bytes.Repeat(sample, (n/len(sample))+1)[:n], 128 } 129 return func(b *testing.B) { 130 for i := 0; i < b.N; i++ { 131 if _, err := Marshal(v); err != nil { 132 b.Fatal("Marshal:", err) 133 } 134 } 135 } 136 } 137 138 func BenchmarkMarshalBytes(b *testing.B) { 139 b.ReportAllocs() 140 // 32 fits within encodeState.scratch. 141 b.Run("32", benchMarshalBytes(32)) 142 // 256 doesn't fit in encodeState.scratch, but is small enough to 143 // allocate and avoid the slower base64.NewEncoder. 144 b.Run("256", benchMarshalBytes(256)) 145 // 4096 is large enough that we want to avoid allocating for it. 146 b.Run("4096", benchMarshalBytes(4096)) 147 } 148 149 func BenchmarkCodeDecoder(b *testing.B) { 150 b.ReportAllocs() 151 if codeJSON == nil { 152 b.StopTimer() 153 codeInit() 154 b.StartTimer() 155 } 156 b.RunParallel(func(pb *testing.PB) { 157 var buf bytes.Buffer 158 dec := NewDecoder(&buf) 159 var r codeResponse 160 for pb.Next() { 161 buf.Write(codeJSON) 162 // hide EOF 163 buf.WriteByte('\n') 164 buf.WriteByte('\n') 165 buf.WriteByte('\n') 166 if err := dec.Decode(&r); err != nil { 167 b.Fatal("Decode:", err) 168 } 169 } 170 }) 171 b.SetBytes(int64(len(codeJSON))) 172 } 173 174 func BenchmarkUnicodeDecoder(b *testing.B) { 175 b.ReportAllocs() 176 j := []byte(`"\uD83D\uDE01"`) 177 b.SetBytes(int64(len(j))) 178 r := bytes.NewReader(j) 179 dec := NewDecoder(r) 180 var out string 181 b.ResetTimer() 182 for i := 0; i < b.N; i++ { 183 if err := dec.Decode(&out); err != nil { 184 b.Fatal("Decode:", err) 185 } 186 r.Seek(0, 0) 187 } 188 } 189 190 func BenchmarkDecoderStream(b *testing.B) { 191 b.ReportAllocs() 192 b.StopTimer() 193 var buf bytes.Buffer 194 dec := NewDecoder(&buf) 195 buf.WriteString(`"` + strings.Repeat("x", 1000000) + `"` + "\n\n\n") 196 var x interface{} 197 if err := dec.Decode(&x); err != nil { 198 b.Fatal("Decode:", err) 199 } 200 ones := strings.Repeat(" 1\n", 300000) + "\n\n\n" 201 b.StartTimer() 202 for i := 0; i < b.N; i++ { 203 if i%300000 == 0 { 204 buf.WriteString(ones) 205 } 206 x = nil 207 if err := dec.Decode(&x); err != nil || x != 1.0 { 208 b.Fatalf("Decode: %v after %d", err, i) 209 } 210 } 211 } 212 213 func BenchmarkCodeUnmarshal(b *testing.B) { 214 b.ReportAllocs() 215 if codeJSON == nil { 216 b.StopTimer() 217 codeInit() 218 b.StartTimer() 219 } 220 b.RunParallel(func(pb *testing.PB) { 221 for pb.Next() { 222 var r codeResponse 223 if err := Unmarshal(codeJSON, &r); err != nil { 224 b.Fatal("Unmarshal:", err) 225 } 226 } 227 }) 228 b.SetBytes(int64(len(codeJSON))) 229 } 230 231 func BenchmarkCodeUnmarshalReuse(b *testing.B) { 232 b.ReportAllocs() 233 if codeJSON == nil { 234 b.StopTimer() 235 codeInit() 236 b.StartTimer() 237 } 238 b.RunParallel(func(pb *testing.PB) { 239 var r codeResponse 240 for pb.Next() { 241 if err := Unmarshal(codeJSON, &r); err != nil { 242 b.Fatal("Unmarshal:", err) 243 } 244 } 245 }) 246 b.SetBytes(int64(len(codeJSON))) 247 } 248 249 func BenchmarkUnmarshalString(b *testing.B) { 250 b.ReportAllocs() 251 data := []byte(`"hello, world"`) 252 b.RunParallel(func(pb *testing.PB) { 253 var s string 254 for pb.Next() { 255 if err := Unmarshal(data, &s); err != nil { 256 b.Fatal("Unmarshal:", err) 257 } 258 } 259 }) 260 } 261 262 func BenchmarkUnmarshalFloat64(b *testing.B) { 263 b.ReportAllocs() 264 data := []byte(`3.14`) 265 b.RunParallel(func(pb *testing.PB) { 266 var f float64 267 for pb.Next() { 268 if err := Unmarshal(data, &f); err != nil { 269 b.Fatal("Unmarshal:", err) 270 } 271 } 272 }) 273 } 274 275 func BenchmarkUnmarshalInt64(b *testing.B) { 276 b.ReportAllocs() 277 data := []byte(`3`) 278 b.RunParallel(func(pb *testing.PB) { 279 var x int64 280 for pb.Next() { 281 if err := Unmarshal(data, &x); err != nil { 282 b.Fatal("Unmarshal:", err) 283 } 284 } 285 }) 286 } 287 288 func BenchmarkIssue10335(b *testing.B) { 289 b.ReportAllocs() 290 j := []byte(`{"a":{ }}`) 291 b.RunParallel(func(pb *testing.PB) { 292 var s struct{} 293 for pb.Next() { 294 if err := Unmarshal(j, &s); err != nil { 295 b.Fatal(err) 296 } 297 } 298 }) 299 } 300 301 func BenchmarkUnmapped(b *testing.B) { 302 b.ReportAllocs() 303 j := []byte(`{"s": "hello", "y": 2, "o": {"x": 0}, "a": [1, 99, {"x": 1}]}`) 304 b.RunParallel(func(pb *testing.PB) { 305 var s struct{} 306 for pb.Next() { 307 if err := Unmarshal(j, &s); err != nil { 308 b.Fatal(err) 309 } 310 } 311 }) 312 } 313 314 func BenchmarkTypeFieldsCache(b *testing.B) { 315 b.ReportAllocs() 316 var maxTypes int = 1e6 317 if testenv.Builder() != "" { 318 maxTypes = 1e3 // restrict cache sizes on builders 319 } 320 321 // Dynamically generate many new types. 322 types := make([]reflect.Type, maxTypes) 323 fs := []reflect.StructField{{ 324 Type: reflect.TypeOf(""), 325 Index: []int{0}, 326 }} 327 for i := range types { 328 fs[0].Name = fmt.Sprintf("TypeFieldsCache%d", i) 329 types[i] = reflect.StructOf(fs) 330 } 331 332 // clearClear clears the cache. Other JSON operations, must not be running. 333 clearCache := func() { 334 fieldCache = sync.Map{} 335 } 336 337 // MissTypes tests the performance of repeated cache misses. 338 // This measures the time to rebuild a cache of size nt. 339 for nt := 1; nt <= maxTypes; nt *= 10 { 340 ts := types[:nt] 341 b.Run(fmt.Sprintf("MissTypes%d", nt), func(b *testing.B) { 342 nc := runtime.GOMAXPROCS(0) 343 for i := 0; i < b.N; i++ { 344 clearCache() 345 var wg sync.WaitGroup 346 for j := 0; j < nc; j++ { 347 wg.Add(1) 348 go func(j int) { 349 for _, t := range ts[(j*len(ts))/nc : ((j+1)*len(ts))/nc] { 350 cachedTypeFields(t) 351 } 352 wg.Done() 353 }(j) 354 } 355 wg.Wait() 356 } 357 }) 358 } 359 360 // HitTypes tests the performance of repeated cache hits. 361 // This measures the average time of each cache lookup. 362 for nt := 1; nt <= maxTypes; nt *= 10 { 363 // Pre-warm a cache of size nt. 364 clearCache() 365 for _, t := range types[:nt] { 366 cachedTypeFields(t) 367 } 368 b.Run(fmt.Sprintf("HitTypes%d", nt), func(b *testing.B) { 369 b.RunParallel(func(pb *testing.PB) { 370 for pb.Next() { 371 cachedTypeFields(types[0]) 372 } 373 }) 374 }) 375 } 376 }