Skip to content

Commit dbaa42f

Browse files
authored
Merge pull request #67 from Basekick-Labs/perf/bsr-fastpaths-omitempty-pool
perf: bsr fast paths for readCode/PeekCode + pool OmitEmpty slices
2 parents c0a3add + 5ebbeb9 commit dbaa42f

4 files changed

Lines changed: 65 additions & 4 deletions

File tree

CHANGELOG.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,14 @@
1-
## v6 (Basekick-Labs fork)
1+
## v6.1 (unreleased)
2+
3+
### Performance
4+
5+
- **decode:** `readCode` bsr fast path — when decoding from a byte slice, reads directly from the underlying array instead of dispatching through the `io.ByteReader` interface; eliminates ~900M interface calls/sec at Arc's throughput ([#57](https://github.com/Basekick-Labs/msgpack/issues/57)) (StructUnmarshal **-7.5%**, StructUnmarshalPartially **-6.1%**)
6+
- **decode:** `PeekCode` bsr fast path — peeks directly at `bsr.data[bsr.pos]` instead of `ReadByte` + `UnreadByte` (two interface calls) ([#59](https://github.com/Basekick-Labs/msgpack/issues/59))
7+
- **encode:** pool `OmitEmpty` filtered field slices via `sync.Pool` — when fields are actually omitted, the allocated `[]*field` slice is now returned to a pool for reuse instead of being GC'd ([#58](https://github.com/Basekick-Labs/msgpack/issues/58))
8+
9+
---
10+
11+
## v6.0.0 (Basekick-Labs fork)
212

313
### Performance
414

decode.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -610,6 +610,14 @@ func (d *Decoder) DecodeRaw() (RawMessage, error) {
610610
// PeekCode returns the next MessagePack code without advancing the reader.
611611
// Subpackage msgpack/codes defines the list of available msgpcode.
612612
func (d *Decoder) PeekCode() (byte, error) {
613+
// Fast path: when decoding from a byte slice, peek directly
614+
// to avoid two interface method calls (ReadByte + UnreadByte).
615+
if d.s == &d.bsr {
616+
if d.bsr.pos >= len(d.bsr.data) {
617+
return 0, io.EOF
618+
}
619+
return d.bsr.data[d.bsr.pos], nil
620+
}
613621
c, err := d.s.ReadByte()
614622
if err != nil {
615623
return 0, err
@@ -634,6 +642,19 @@ func (d *Decoder) hasNilCode() bool {
634642
}
635643

636644
func (d *Decoder) readCode() (byte, error) {
645+
// Fast path: when decoding from a byte slice, read directly
646+
// to avoid interface method dispatch on the hottest decode function.
647+
if d.s == &d.bsr {
648+
if d.bsr.pos >= len(d.bsr.data) {
649+
return 0, io.EOF
650+
}
651+
c := d.bsr.data[d.bsr.pos]
652+
d.bsr.pos++
653+
if d.rec != nil {
654+
d.rec = append(d.rec, c)
655+
}
656+
return c, nil
657+
}
637658
c, err := d.s.ReadByte()
638659
if err != nil {
639660
return 0, err

encode_map.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,21 +279,40 @@ func encodeStructValue(e *Encoder, strct reflect.Value) error {
279279
fields := structFields.OmitEmpty(e, strct)
280280

281281
if err := e.EncodeMapLen(len(fields)); err != nil {
282+
putFilteredFields(structFields, fields)
282283
return err
283284
}
284285

285286
for _, f := range fields {
286287
if err := e.EncodeString(f.name); err != nil {
288+
putFilteredFields(structFields, fields)
287289
return err
288290
}
289291
if err := f.EncodeValue(e, strct); err != nil {
292+
putFilteredFields(structFields, fields)
290293
return err
291294
}
292295
}
293296

297+
putFilteredFields(structFields, fields)
294298
return nil
295299
}
296300

301+
// putFilteredFields returns a pooled filtered field slice.
302+
// It is a no-op when the slice is the original fs.List (not pooled).
303+
func putFilteredFields(fs *fields, filtered []*field) {
304+
if len(filtered) > 0 && len(fs.List) > 0 && &filtered[0] == &fs.List[0] {
305+
return
306+
}
307+
for i := range filtered {
308+
filtered[i] = nil
309+
}
310+
if cap(filtered) <= 64 {
311+
s := filtered
312+
filteredFieldsPool.Put(&s)
313+
}
314+
}
315+
297316
func encodeStructValueAsArray(e *Encoder, strct reflect.Value, fields []*field) error {
298317
if err := e.EncodeArrayLen(len(fields)); err != nil {
299318
return err

types.go

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,13 @@ func (f *field) DecodeValue(d *Decoder, strct reflect.Value) error {
120120
return f.decoder(d, v)
121121
}
122122

123+
var filteredFieldsPool = sync.Pool{
124+
New: func() interface{} {
125+
s := make([]*field, 0, 16)
126+
return &s
127+
},
128+
}
129+
123130
//------------------------------------------------------------------------------
124131

125132
type fields struct {
@@ -172,14 +179,18 @@ func (fs *fields) OmitEmpty(e *Encoder, strct reflect.Value) []*field {
172179
return fs.List
173180
}
174181

175-
// Second pass: build the filtered slice only when necessary.
176-
fields := make([]*field, 0, n)
182+
// Second pass: build the filtered slice from pool.
183+
sp := filteredFieldsPool.Get().(*[]*field)
184+
fields := (*sp)[:0]
185+
if cap(fields) < n {
186+
fields = make([]*field, 0, n)
187+
}
177188
for _, f := range fs.List {
178189
if !f.Omit(e, strct) {
179190
fields = append(fields, f)
180191
}
181192
}
182-
193+
*sp = fields
183194
return fields
184195
}
185196

0 commit comments

Comments
 (0)