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