Skip to content

Commit be78ef3

Browse files
authored
Improve read speed (#361)
* Improve read speed Utilize philhofer/fwd#32 to peek bytes Eliminate bounds checks, mostly on reading sizes, but also integers. ``` benchmark old MB/s new MB/s speedup BenchmarkReadMapHeaderBytes-32 1136.75 1136.89 1.00x BenchmarkReadArrayHeaderBytes-32 1133.52 1149.34 1.01x BenchmarkTestReadBytesHeader-32 1200.18 1171.21 0.98x BenchmarkReadNilByte-32 2907.50 2176.63 0.75x BenchmarkReadFloat64Bytes-32 4308.86 3869.64 0.90x BenchmarkReadFloat32Bytes-32 2354.55 2332.21 0.99x BenchmarkReadBoolBytes-32 863.66 1124.07 1.30x BenchmarkReadTimeBytes-32 3710.27 3668.07 0.99x BenchmarkReadMapHeader-32 228.74 391.76 1.71x BenchmarkReadArrayHeader-32 243.90 454.31 1.86x BenchmarkReadNil-32 127.42 191.55 1.50x BenchmarkReadFloat64-32 1046.08 1142.49 1.09x BenchmarkReadFloat32-32 595.67 669.65 1.12x BenchmarkReadInt64-32 422.29 798.35 1.89x BenchmarkReadUintWithInt64-32 186.59 371.28 1.99x BenchmarkReadUint64-32 282.87 377.76 1.34x BenchmarkReadIntWithUint64-32 465.30 684.49 1.47x BenchmarkRead16Bytes-32 1021.15 1021.38 1.00x BenchmarkRead256Bytes-32 5324.42 6166.06 1.16x BenchmarkRead2048Bytes-32 8405.41 8382.61 1.00x BenchmarkRead16StringAsBytes-32 1102.72 1321.71 1.20x BenchmarkRead256StringAsBytes-32 6959.78 6627.49 0.95x BenchmarkRead16String-32 320.75 378.46 1.18x BenchmarkRead256String-32 2066.40 2242.78 1.09x BenchmarkReadComplex64-32 900.13 1064.21 1.18x BenchmarkReadComplex128-32 1809.88 2028.11 1.12x BenchmarkReadTime-32 1362.46 1425.79 1.05x ``` Apologies for the noisy benchmark. `BenchmarkReadNilByte` and `BenchmarkReadFloat64Byte` does not touch new code, so it seems to be "micro-bench-itis", where random deltas show up. Bench takes ~30m to run for whatever reason. The compiler is not clever enough to track back to `l := len(b)`, so it inserts a bounds check. Also `len(b) < 3` and `big.Uint16(b[1:])` causes a bounds check. So we rewrite those to avoid it. This should cover the lowest hanging fruits. * Fix up ReadExactBytes - do ReadArrayHeaderBytes as well. * Don't stop the timer. Makes benchmarks take forever. Simplify EndlessReader
1 parent b78c5cd commit be78ef3

File tree

8 files changed

+148
-159
lines changed

8 files changed

+148
-159
lines changed

go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ module github.com/tinylib/msgp
33
go 1.20
44

55
require (
6-
github.com/philhofer/fwd v1.1.3-0.20240612014219-fbbf4953d986
6+
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c
77
golang.org/x/tools v0.22.0
88
)
99

go.sum

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
github.com/philhofer/fwd v1.1.3-0.20240612014219-fbbf4953d986 h1:jYi87L8j62qkXzaYHAQAhEapgukhenIMZRBKTNRLHJ4=
2-
github.com/philhofer/fwd v1.1.3-0.20240612014219-fbbf4953d986/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
1+
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c h1:dAMKvw0MlJT1GshSTtih8C2gDs04w8dReiOGXrGLNoY=
2+
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
33
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
44
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
55
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=

msgp/circular.go

+8-6
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ type EndlessReader struct {
1616

1717
// NewEndlessReader returns a new endless reader
1818
func NewEndlessReader(b []byte, tb timer) *EndlessReader {
19+
// Double until we reach 4K.
20+
for len(b) < 4<<10 {
21+
b = append(b, b...)
22+
}
1923
return &EndlessReader{tb: tb, data: b, offset: 0}
2024
}
2125

@@ -24,16 +28,14 @@ func NewEndlessReader(b []byte, tb timer) *EndlessReader {
2428
// fills the supplied slice while the benchmark
2529
// timer is stopped.
2630
func (c *EndlessReader) Read(p []byte) (int, error) {
27-
c.tb.StopTimer()
2831
var n int
2932
l := len(p)
3033
m := len(c.data)
34+
nn := copy(p[n:], c.data[c.offset:])
35+
n += nn
3136
for n < l {
32-
nn := copy(p[n:], c.data[c.offset:])
33-
n += nn
34-
c.offset += nn
35-
c.offset %= m
37+
n += copy(p[n:], c.data[:])
3638
}
37-
c.tb.StartTimer()
39+
c.offset = (c.offset + l) % m
3840
return n, nil
3941
}

msgp/json.go

+2-4
Original file line numberDiff line numberDiff line change
@@ -346,14 +346,12 @@ func rwExtension(dst jsWriter, src *Reader) (n int, err error) {
346346
}
347347

348348
func rwString(dst jsWriter, src *Reader) (n int, err error) {
349-
var p []byte
350-
p, err = src.R.Peek(1)
349+
lead, err := src.R.PeekByte()
351350
if err != nil {
352351
return
353352
}
354-
lead := p[0]
355353
var read int
356-
354+
var p []byte
357355
if isfixstr(lead) {
358356
read = int(rfixstr(lead))
359357
src.R.Skip(1)

msgp/read.go

+28-41
Original file line numberDiff line numberDiff line change
@@ -236,13 +236,13 @@ func (m *Reader) BufferSize() int { return m.R.BufferSize() }
236236

237237
// NextType returns the next object type to be decoded.
238238
func (m *Reader) NextType() (Type, error) {
239-
p, err := m.R.Peek(1)
239+
next, err := m.R.PeekByte()
240240
if err != nil {
241241
return InvalidType, err
242242
}
243-
t := getType(p[0])
243+
t := getType(next)
244244
if t == InvalidType {
245-
return t, InvalidPrefixError(p[0])
245+
return t, InvalidPrefixError(next)
246246
}
247247
if t == ExtensionType {
248248
v, err := m.peekExtensionType()
@@ -264,8 +264,8 @@ func (m *Reader) NextType() (Type, error) {
264264
// IsNil returns whether or not
265265
// the next byte is a null messagepack byte
266266
func (m *Reader) IsNil() bool {
267-
p, err := m.R.Peek(1)
268-
return err == nil && p[0] == mnil
267+
p, err := m.R.PeekByte()
268+
return err == nil && p == mnil
269269
}
270270

271271
// getNextSize returns the size of the next object on the wire.
@@ -276,11 +276,10 @@ func (m *Reader) IsNil() bool {
276276
// use uintptr b/c it's guaranteed to be large enough
277277
// to hold whatever we can fit in memory.
278278
func getNextSize(r *fwd.Reader) (uintptr, uintptr, error) {
279-
b, err := r.Peek(1)
279+
lead, err := r.PeekByte()
280280
if err != nil {
281281
return 0, 0, err
282282
}
283-
lead := b[0]
284283
spec := getBytespec(lead)
285284
size, mode := spec.size, spec.extra
286285
if size == 0 {
@@ -289,7 +288,7 @@ func getNextSize(r *fwd.Reader) (uintptr, uintptr, error) {
289288
if mode >= 0 {
290289
return uintptr(size), uintptr(mode), nil
291290
}
292-
b, err = r.Peek(int(size))
291+
b, err := r.Peek(int(size))
293292
if err != nil {
294293
return 0, 0, err
295294
}
@@ -373,11 +372,10 @@ func (m *Reader) Skip() error {
373372
func (m *Reader) ReadMapHeader() (sz uint32, err error) {
374373
var p []byte
375374
var lead byte
376-
p, err = m.R.Peek(1)
375+
lead, err = m.R.PeekByte()
377376
if err != nil {
378377
return
379378
}
380-
lead = p[0]
381379
if isfixmap(lead) {
382380
sz = uint32(rfixmap(lead))
383381
_, err = m.R.Skip(1)
@@ -427,12 +425,12 @@ func (m *Reader) ReadMapKey(scratch []byte) ([]byte, error) {
427425
// method; writing into the returned slice may
428426
// corrupt future reads.
429427
func (m *Reader) ReadMapKeyPtr() ([]byte, error) {
430-
p, err := m.R.Peek(1)
428+
lead, err := m.R.PeekByte()
431429
if err != nil {
432430
return nil, err
433431
}
434-
lead := p[0]
435432
var read int
433+
var p []byte
436434
if isfixstr(lead) {
437435
read = int(rfixstr(lead))
438436
m.R.Skip(1)
@@ -471,18 +469,16 @@ fill:
471469
// array header and returns the size of the array
472470
// and the number of bytes read.
473471
func (m *Reader) ReadArrayHeader() (sz uint32, err error) {
474-
var lead byte
475-
var p []byte
476-
p, err = m.R.Peek(1)
472+
lead, err := m.R.PeekByte()
477473
if err != nil {
478474
return
479475
}
480-
lead = p[0]
481476
if isfixarray(lead) {
482477
sz = uint32(rfixarray(lead))
483478
_, err = m.R.Skip(1)
484479
return
485480
}
481+
var p []byte
486482
switch lead {
487483
case marray16:
488484
p, err = m.R.Next(3)
@@ -508,12 +504,12 @@ func (m *Reader) ReadArrayHeader() (sz uint32, err error) {
508504

509505
// ReadNil reads a 'nil' MessagePack byte from the reader
510506
func (m *Reader) ReadNil() error {
511-
p, err := m.R.Peek(1)
507+
p, err := m.R.PeekByte()
512508
if err != nil {
513509
return err
514510
}
515-
if p[0] != mnil {
516-
return badPrefix(NilType, p[0])
511+
if p != mnil {
512+
return badPrefix(NilType, p)
517513
}
518514
_, err = m.R.Skip(1)
519515
return err
@@ -566,17 +562,17 @@ func (m *Reader) ReadFloat32() (f float32, err error) {
566562

567563
// ReadBool reads a bool from the reader
568564
func (m *Reader) ReadBool() (b bool, err error) {
569-
var p []byte
570-
p, err = m.R.Peek(1)
565+
var p byte
566+
p, err = m.R.PeekByte()
571567
if err != nil {
572568
return
573569
}
574-
switch p[0] {
570+
switch p {
575571
case mtrue:
576572
b = true
577573
case mfalse:
578574
default:
579-
err = badPrefix(BoolType, p[0])
575+
err = badPrefix(BoolType, p)
580576
return
581577
}
582578
_, err = m.R.Skip(1)
@@ -592,12 +588,10 @@ func (m *Reader) ReadDuration() (d time.Duration, err error) {
592588
// ReadInt64 reads an int64 from the reader
593589
func (m *Reader) ReadInt64() (i int64, err error) {
594590
var p []byte
595-
var lead byte
596-
p, err = m.R.Peek(1)
591+
lead, err := m.R.PeekByte()
597592
if err != nil {
598593
return
599594
}
600-
lead = p[0]
601595

602596
if isfixint(lead) {
603597
i = int64(rfixint(lead))
@@ -738,12 +732,10 @@ func (m *Reader) ReadInt() (i int, err error) {
738732
// ReadUint64 reads a uint64 from the reader
739733
func (m *Reader) ReadUint64() (u uint64, err error) {
740734
var p []byte
741-
var lead byte
742-
p, err = m.R.Peek(1)
735+
lead, err := m.R.PeekByte()
743736
if err != nil {
744737
return
745738
}
746-
lead = p[0]
747739
if isfixint(lead) {
748740
u = uint64(rfixint(lead))
749741
_, err = m.R.Skip(1)
@@ -958,11 +950,11 @@ func (m *Reader) ReadBytes(scratch []byte) (b []byte, err error) {
958950
// way.
959951
func (m *Reader) ReadBytesHeader() (sz uint32, err error) {
960952
var p []byte
961-
p, err = m.R.Peek(1)
953+
lead, err := m.R.PeekByte()
962954
if err != nil {
963955
return
964956
}
965-
switch p[0] {
957+
switch lead {
966958
case mbin8:
967959
p, err = m.R.Next(2)
968960
if err != nil {
@@ -1036,12 +1028,10 @@ func (m *Reader) ReadExactBytes(into []byte) error {
10361028
// if it is non-nil.
10371029
func (m *Reader) ReadStringAsBytes(scratch []byte) (b []byte, err error) {
10381030
var p []byte
1039-
var lead byte
1040-
p, err = m.R.Peek(1)
1031+
lead, err := m.R.PeekByte()
10411032
if err != nil {
10421033
return
10431034
}
1044-
lead = p[0]
10451035
var read int64
10461036

10471037
if isfixstr(lead) {
@@ -1088,17 +1078,16 @@ fill:
10881078
// for dealing with the next 'sz' bytes from
10891079
// the reader in an application-specific manner.
10901080
func (m *Reader) ReadStringHeader() (sz uint32, err error) {
1091-
var p []byte
1092-
p, err = m.R.Peek(1)
1081+
lead, err := m.R.PeekByte()
10931082
if err != nil {
10941083
return
10951084
}
1096-
lead := p[0]
10971085
if isfixstr(lead) {
10981086
sz = uint32(rfixstr(lead))
10991087
m.R.Skip(1)
11001088
return
11011089
}
1090+
var p []byte
11021091
switch lead {
11031092
case mstr8:
11041093
p, err = m.R.Next(2)
@@ -1129,15 +1118,13 @@ func (m *Reader) ReadStringHeader() (sz uint32, err error) {
11291118

11301119
// ReadString reads a utf-8 string from the reader
11311120
func (m *Reader) ReadString() (s string, err error) {
1132-
var p []byte
1133-
var lead byte
11341121
var read int64
1135-
p, err = m.R.Peek(1)
1122+
lead, err := m.R.PeekByte()
11361123
if err != nil {
11371124
return
11381125
}
1139-
lead = p[0]
11401126

1127+
var p []byte
11411128
if isfixstr(lead) {
11421129
read = int64(rfixstr(lead))
11431130
m.R.Skip(1)

0 commit comments

Comments
 (0)