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
5package gob
6
7import (
8	"bytes"
9	"io"
10	"os"
11	"reflect"
12	"runtime"
13	"testing"
14)
15
16type Bench struct {
17	A int
18	B float64
19	C string
20	D []byte
21}
22
23func benchmarkEndToEnd(b *testing.B, ctor func() any, pipe func() (r io.Reader, w io.Writer, err error)) {
24	b.ReportAllocs()
25	b.RunParallel(func(pb *testing.PB) {
26		r, w, err := pipe()
27		if err != nil {
28			b.Fatal("can't get pipe:", err)
29		}
30		v := ctor()
31		enc := NewEncoder(w)
32		dec := NewDecoder(r)
33		for pb.Next() {
34			if err := enc.Encode(v); err != nil {
35				b.Fatal("encode error:", err)
36			}
37			if err := dec.Decode(v); err != nil {
38				b.Fatal("decode error:", err)
39			}
40		}
41	})
42}
43
44func BenchmarkEndToEndPipe(b *testing.B) {
45	benchmarkEndToEnd(b, func() any {
46		return &Bench{7, 3.2, "now is the time", bytes.Repeat([]byte("for all good men"), 100)}
47	}, func() (r io.Reader, w io.Writer, err error) {
48		r, w, err = os.Pipe()
49		return
50	})
51}
52
53func BenchmarkEndToEndByteBuffer(b *testing.B) {
54	benchmarkEndToEnd(b, func() any {
55		return &Bench{7, 3.2, "now is the time", bytes.Repeat([]byte("for all good men"), 100)}
56	}, func() (r io.Reader, w io.Writer, err error) {
57		var buf bytes.Buffer
58		return &buf, &buf, nil
59	})
60}
61
62func BenchmarkEndToEndSliceByteBuffer(b *testing.B) {
63	benchmarkEndToEnd(b, func() any {
64		v := &Bench{7, 3.2, "now is the time", nil}
65		Register(v)
66		arr := make([]any, 100)
67		for i := range arr {
68			arr[i] = v
69		}
70		return &arr
71	}, func() (r io.Reader, w io.Writer, err error) {
72		var buf bytes.Buffer
73		return &buf, &buf, nil
74	})
75}
76
77func TestCountEncodeMallocs(t *testing.T) {
78	if testing.Short() {
79		t.Skip("skipping malloc count in short mode")
80	}
81	if runtime.GOMAXPROCS(0) > 1 {
82		t.Skip("skipping; GOMAXPROCS>1")
83	}
84
85	const N = 1000
86
87	var buf bytes.Buffer
88	enc := NewEncoder(&buf)
89	bench := &Bench{7, 3.2, "now is the time", []byte("for all good men")}
90
91	allocs := testing.AllocsPerRun(N, func() {
92		err := enc.Encode(bench)
93		if err != nil {
94			t.Fatal("encode:", err)
95		}
96	})
97	if allocs != 0 {
98		t.Fatalf("mallocs per encode of type Bench: %v; wanted 0\n", allocs)
99	}
100}
101
102func TestCountDecodeMallocs(t *testing.T) {
103	if testing.Short() {
104		t.Skip("skipping malloc count in short mode")
105	}
106	if runtime.GOMAXPROCS(0) > 1 {
107		t.Skip("skipping; GOMAXPROCS>1")
108	}
109
110	const N = 1000
111
112	var buf bytes.Buffer
113	enc := NewEncoder(&buf)
114	bench := &Bench{7, 3.2, "now is the time", []byte("for all good men")}
115
116	// Fill the buffer with enough to decode
117	testing.AllocsPerRun(N, func() {
118		err := enc.Encode(bench)
119		if err != nil {
120			t.Fatal("encode:", err)
121		}
122	})
123
124	dec := NewDecoder(&buf)
125	allocs := testing.AllocsPerRun(N, func() {
126		*bench = Bench{}
127		err := dec.Decode(&bench)
128		if err != nil {
129			t.Fatal("decode:", err)
130		}
131	})
132	if allocs != 3 {
133		t.Fatalf("mallocs per decode of type Bench: %v; wanted 3\n", allocs)
134	}
135}
136
137func benchmarkEncodeSlice(b *testing.B, a any) {
138	b.ResetTimer()
139	b.ReportAllocs()
140	b.RunParallel(func(pb *testing.PB) {
141		var buf bytes.Buffer
142		enc := NewEncoder(&buf)
143
144		for pb.Next() {
145			buf.Reset()
146			err := enc.Encode(a)
147			if err != nil {
148				b.Fatal(err)
149			}
150		}
151	})
152}
153
154func BenchmarkEncodeComplex128Slice(b *testing.B) {
155	a := make([]complex128, 1000)
156	for i := range a {
157		a[i] = 1.2 + 3.4i
158	}
159	benchmarkEncodeSlice(b, a)
160}
161
162func BenchmarkEncodeFloat64Slice(b *testing.B) {
163	a := make([]float64, 1000)
164	for i := range a {
165		a[i] = 1.23e4
166	}
167	benchmarkEncodeSlice(b, a)
168}
169
170func BenchmarkEncodeInt32Slice(b *testing.B) {
171	a := make([]int32, 1000)
172	for i := range a {
173		a[i] = int32(i * 100)
174	}
175	benchmarkEncodeSlice(b, a)
176}
177
178func BenchmarkEncodeStringSlice(b *testing.B) {
179	a := make([]string, 1000)
180	for i := range a {
181		a[i] = "now is the time"
182	}
183	benchmarkEncodeSlice(b, a)
184}
185
186func BenchmarkEncodeInterfaceSlice(b *testing.B) {
187	a := make([]any, 1000)
188	for i := range a {
189		a[i] = "now is the time"
190	}
191	benchmarkEncodeSlice(b, a)
192}
193
194// benchmarkBuf is a read buffer we can reset
195type benchmarkBuf struct {
196	offset int
197	data   []byte
198}
199
200func (b *benchmarkBuf) Read(p []byte) (n int, err error) {
201	n = copy(p, b.data[b.offset:])
202	if n == 0 {
203		return 0, io.EOF
204	}
205	b.offset += n
206	return
207}
208
209func (b *benchmarkBuf) ReadByte() (c byte, err error) {
210	if b.offset >= len(b.data) {
211		return 0, io.EOF
212	}
213	c = b.data[b.offset]
214	b.offset++
215	return
216}
217
218func (b *benchmarkBuf) reset() {
219	b.offset = 0
220}
221
222func benchmarkDecodeSlice(b *testing.B, a any) {
223	var buf bytes.Buffer
224	enc := NewEncoder(&buf)
225	err := enc.Encode(a)
226	if err != nil {
227		b.Fatal(err)
228	}
229
230	ra := reflect.ValueOf(a)
231	rt := ra.Type()
232	b.ResetTimer()
233
234	b.ReportAllocs()
235	b.RunParallel(func(pb *testing.PB) {
236		// TODO(#19025): Move per-thread allocation before ResetTimer.
237		rp := reflect.New(rt)
238		rp.Elem().Set(reflect.MakeSlice(rt, ra.Len(), ra.Cap()))
239		p := rp.Interface()
240
241		bbuf := benchmarkBuf{data: buf.Bytes()}
242
243		for pb.Next() {
244			bbuf.reset()
245			dec := NewDecoder(&bbuf)
246			err := dec.Decode(p)
247			if err != nil {
248				b.Fatal(err)
249			}
250		}
251	})
252}
253
254func BenchmarkDecodeComplex128Slice(b *testing.B) {
255	a := make([]complex128, 1000)
256	for i := range a {
257		a[i] = 1.2 + 3.4i
258	}
259	benchmarkDecodeSlice(b, a)
260}
261
262func BenchmarkDecodeFloat64Slice(b *testing.B) {
263	a := make([]float64, 1000)
264	for i := range a {
265		a[i] = 1.23e4
266	}
267	benchmarkDecodeSlice(b, a)
268}
269
270func BenchmarkDecodeInt32Slice(b *testing.B) {
271	a := make([]int32, 1000)
272	for i := range a {
273		a[i] = 1234
274	}
275	benchmarkDecodeSlice(b, a)
276}
277
278func BenchmarkDecodeStringSlice(b *testing.B) {
279	a := make([]string, 1000)
280	for i := range a {
281		a[i] = "now is the time"
282	}
283	benchmarkDecodeSlice(b, a)
284}
285func BenchmarkDecodeStringsSlice(b *testing.B) {
286	a := make([][]string, 1000)
287	for i := range a {
288		a[i] = []string{"now is the time"}
289	}
290	benchmarkDecodeSlice(b, a)
291}
292func BenchmarkDecodeBytesSlice(b *testing.B) {
293	a := make([][]byte, 1000)
294	for i := range a {
295		a[i] = []byte("now is the time")
296	}
297	benchmarkDecodeSlice(b, a)
298}
299
300func BenchmarkDecodeInterfaceSlice(b *testing.B) {
301	a := make([]any, 1000)
302	for i := range a {
303		a[i] = "now is the time"
304	}
305	benchmarkDecodeSlice(b, a)
306}
307
308func BenchmarkDecodeMap(b *testing.B) {
309	count := 1000
310	m := make(map[int]int, count)
311	for i := 0; i < count; i++ {
312		m[i] = i
313	}
314	var buf bytes.Buffer
315	enc := NewEncoder(&buf)
316	err := enc.Encode(m)
317	if err != nil {
318		b.Fatal(err)
319	}
320	bbuf := benchmarkBuf{data: buf.Bytes()}
321	b.ResetTimer()
322	b.ReportAllocs()
323	for i := 0; i < b.N; i++ {
324		var rm map[int]int
325		bbuf.reset()
326		dec := NewDecoder(&bbuf)
327		err := dec.Decode(&rm)
328		if err != nil {
329			b.Fatal(i, err)
330		}
331	}
332}
333