Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
## v6 (Basekick-Labs fork)
## v6.1 (unreleased)

### Performance

- **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%**)
- **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))
- **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))

---

## v6.0.0 (Basekick-Labs fork)

### Performance

Expand Down
21 changes: 21 additions & 0 deletions decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -610,6 +610,14 @@ func (d *Decoder) DecodeRaw() (RawMessage, error) {
// PeekCode returns the next MessagePack code without advancing the reader.
// Subpackage msgpack/codes defines the list of available msgpcode.
func (d *Decoder) PeekCode() (byte, error) {
// Fast path: when decoding from a byte slice, peek directly
// to avoid two interface method calls (ReadByte + UnreadByte).
if d.s == &d.bsr {
if d.bsr.pos >= len(d.bsr.data) {
return 0, io.EOF
}
return d.bsr.data[d.bsr.pos], nil
}
c, err := d.s.ReadByte()
if err != nil {
return 0, err
Expand All @@ -634,6 +642,19 @@ func (d *Decoder) hasNilCode() bool {
}

func (d *Decoder) readCode() (byte, error) {
// Fast path: when decoding from a byte slice, read directly
// to avoid interface method dispatch on the hottest decode function.
if d.s == &d.bsr {
if d.bsr.pos >= len(d.bsr.data) {
return 0, io.EOF
}
c := d.bsr.data[d.bsr.pos]
d.bsr.pos++
if d.rec != nil {
d.rec = append(d.rec, c)
}
return c, nil
}
c, err := d.s.ReadByte()
if err != nil {
return 0, err
Expand Down
19 changes: 19 additions & 0 deletions encode_map.go
Original file line number Diff line number Diff line change
Expand Up @@ -279,21 +279,40 @@ func encodeStructValue(e *Encoder, strct reflect.Value) error {
fields := structFields.OmitEmpty(e, strct)

if err := e.EncodeMapLen(len(fields)); err != nil {
putFilteredFields(structFields, fields)
return err
}

for _, f := range fields {
if err := e.EncodeString(f.name); err != nil {
putFilteredFields(structFields, fields)
return err
}
if err := f.EncodeValue(e, strct); err != nil {
putFilteredFields(structFields, fields)
return err
}
}

putFilteredFields(structFields, fields)
return nil
}

// putFilteredFields returns a pooled filtered field slice.
// It is a no-op when the slice is the original fs.List (not pooled).
func putFilteredFields(fs *fields, filtered []*field) {
if len(filtered) > 0 && len(fs.List) > 0 && &filtered[0] == &fs.List[0] {
return
}
for i := range filtered {
filtered[i] = nil
}
if cap(filtered) <= 64 {
s := filtered
filteredFieldsPool.Put(&s)
}
}

func encodeStructValueAsArray(e *Encoder, strct reflect.Value, fields []*field) error {
if err := e.EncodeArrayLen(len(fields)); err != nil {
return err
Expand Down
17 changes: 14 additions & 3 deletions types.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,13 @@ func (f *field) DecodeValue(d *Decoder, strct reflect.Value) error {
return f.decoder(d, v)
}

var filteredFieldsPool = sync.Pool{
New: func() interface{} {
s := make([]*field, 0, 16)
return &s
},
}

//------------------------------------------------------------------------------

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

// Second pass: build the filtered slice only when necessary.
fields := make([]*field, 0, n)
// Second pass: build the filtered slice from pool.
sp := filteredFieldsPool.Get().(*[]*field)
fields := (*sp)[:0]
if cap(fields) < n {
fields = make([]*field, 0, n)
}
for _, f := range fs.List {
if !f.Omit(e, strct) {
fields = append(fields, f)
}
}

*sp = fields
return fields
}

Expand Down