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