Skip to content

Commit

Permalink
multi: Note benchmarks that perform buffer reuse
Browse files Browse the repository at this point in the history
This adds an explicit note for the benchmarks which perform buffer reuse
during marshalling operations. This improves fairness when comparing the
performance of different benchmarks that may or may not offer this
feature.

Given that buffer reuse is a marshalling feature and unsafe string
decoding is an unmarshalling feature, for serializers that support both
features, they are both coalesced into a single benchmark to avoid
unnecessarily increasing the size of the report.
  • Loading branch information
matheusd committed Jun 17, 2024
1 parent dc627dd commit 4a88092
Show file tree
Hide file tree
Showing 11 changed files with 531 additions and 285 deletions.
56 changes: 52 additions & 4 deletions benchmarks.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,10 @@ type BenchmarkCase struct {
// underlying byte slice is modified.
UnsafeStringUnmarshal bool

// BufferReuseMarshal reocrds whether the serializer re-uses the
// marshalling buffer.
BufferReuseMarshal bool

// TimeSupport records the type of support time.Time values have on
// this encoder.
TimeSupport TimeSupport
Expand Down Expand Up @@ -284,10 +288,11 @@ var benchmarkCases = []BenchmarkCase{
TimeSupport: TSFullTzOffset,
APIKind: AKCodegen,
}, {
Name: "gencode/unsafe",
Name: "gencode/unsafe_reuse",
URL: "github.com/andyleap/gencode",
New: gencode.NewGencodeUnsafeSerializer,

BufferReuseMarshal: true,
UnsafeStringUnmarshal: true,
TimeSupport: TSFullTzOffset,
APIKind: AKCodegen,
Expand Down Expand Up @@ -371,6 +376,17 @@ var benchmarkCases = []BenchmarkCase{
Notes: []string{
"time.Time values are encoded with 100 nanosecond precision.",
},
}, {
Name: "200sc/bebop/reuse",
URL: "github.com/200sc/bebop",
New: bebop200sc.NewBebop200ScReuseSerializer,

BufferReuseMarshal: true,
TimeSupport: TSCustom,
APIKind: AKCodegen,
Notes: []string{
"time.Time values are encoded with 100 nanosecond precision.",
},
}, {
Name: "wellquite/bebop",
URL: "wellquite.org/bebop",
Expand All @@ -381,13 +397,32 @@ var benchmarkCases = []BenchmarkCase{
Notes: []string{
"time.Time values are encoded with 100 nanosecond precision.",
},
}, {
Name: "wellquite/bebop/reuse",
URL: "wellquite.org/bebop",
New: bebopwellquite.NewBebopWellquiteReuseSerializer,

BufferReuseMarshal: true,
TimeSupport: TSCustom,
APIKind: AKCodegen,
Notes: []string{
"time.Time values are encoded with 100 nanosecond precision.",
},
}, {
Name: "fastjson",
URL: "github.com/valyala/fastjson",
New: fastjson.NewFastJSONSerializer,

TimeSupport: TSNoSupport,
APIKind: AKManual,
}, {
Name: "fastjson/reuse",
URL: "github.com/valyala/fastjson",
New: fastjson.NewFastJSONReuseSerializer,

BufferReuseMarshal: true,
TimeSupport: TSNoSupport,
APIKind: AKManual,
}, {
Name: "benc",
URL: "github.com/deneonet/benc",
Expand All @@ -411,18 +446,31 @@ var benchmarkCases = []BenchmarkCase{
TimeSupport: TSNoSupport,
APIKind: AKManual,
}, {
Name: "mus/unsafe",
Name: "mus/unsafe_reuse",
URL: "github.com/mus-format/mus-go",
New: mus.NewMUSUnsafeSerializer,

TimeSupport: TSNoSupport,
APIKind: AKManual,
BufferReuseMarshal: true,
UnsafeStringUnmarshal: true,
TimeSupport: TSNoSupport,
APIKind: AKManual,
}, {
Name: "baseline",
URL: "",
New: baseline.NewBaselineSerializer,

TimeSupport: TSNoSupport,
APIKind: AKManual,
Notes: []string{
"This is a manually written encoding, designed to be the fastest possible for this benchmark.",
},
}, {
Name: "baseline/unsafe_reuse",
URL: "",
New: baseline.NewBaselineUnsafeSerializer,

UnsafeStringUnmarshal: true,
BufferReuseMarshal: true,
TimeSupport: TSNoSupport,
APIKind: AKManual,
Notes: []string{
Expand Down
2 changes: 2 additions & 0 deletions genreport_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type reportLine struct {
UnmarshalIterCount int `json:"unmarshal_iter_count"`
TotalIterCount int `json:"total_iter_count"`
UnsafeStringUnmarshal bool `json:"unsafe_string_unmarshal"`
BufferReuseMarshal bool `json:"buffer_reuse_marshal"`
MarshalNsOp int64 `json:"marshal_ns_op"`
UnmarshalNsOp int64 `json:"unmarshal_ns_op"`
TotalNsOp int64 `json:"total_ns_op"`
Expand Down Expand Up @@ -61,6 +62,7 @@ func generateReport() error {
UnmarshalNsOp: unmarshalRes.NsPerOp(),
TotalNsOp: marshalRes.NsPerOp() + unmarshalRes.NsPerOp(),
UnsafeStringUnmarshal: bench.UnsafeStringUnmarshal,
BufferReuseMarshal: bench.BufferReuseMarshal,
SerializationSize: int64(marshalRes.Extra["B/serial"]),
MarshalAllocBytes: marshalRes.AllocedBytesPerOp(),
UnmarshalAllocBytes: unmarshalRes.AllocedBytesPerOp(),
Expand Down
49 changes: 43 additions & 6 deletions internal/serializers/baseline/baseline.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@ import (
//
// This is useful as an upper bound on the performance achievable for standard
// marshalling/unmarshalling operations.
type BaselineSerializer struct {
b []byte
}
type BaselineSerializer struct{}

// maxSmallStructSerializeSize is the max size of a small struct serialized
// with the baseline serializer.
const maxSmallStructSerializeSize = 8 + 8 + 4 + 1 + goserbench.MaxSmallStructPhoneSize + goserbench.MaxSmallStructNameSize

// appendBool appends a bool to b.
func appendBool(b []byte, v bool) []byte {
Expand All @@ -39,7 +41,7 @@ func getBool(b []byte) bool {

func (b *BaselineSerializer) Marshal(o interface{}) ([]byte, error) {
a := o.(*goserbench.SmallStruct)
buf := b.b[:0]
buf := make([]byte, 0, maxSmallStructSerializeSize)
buf = binary.LittleEndian.AppendUint64(buf, uint64(a.BirthDay.UnixNano()))
buf = binary.LittleEndian.AppendUint64(buf, math.Float64bits(a.Money))
buf = binary.LittleEndian.AppendUint32(buf, uint32(a.Siblings))
Expand All @@ -50,6 +52,39 @@ func (b *BaselineSerializer) Marshal(o interface{}) ([]byte, error) {
}

func (b *BaselineSerializer) Unmarshal(d []byte, o interface{}) error {
a := o.(*goserbench.SmallStruct)
a.BirthDay = time.Unix(0, int64(binary.LittleEndian.Uint64(d[:8])))
a.Money = math.Float64frombits(binary.LittleEndian.Uint64(d[8:16]))
a.Siblings = int(binary.LittleEndian.Uint32(d[16:20]))
a.Name = string(d[20:36])
a.Phone = string(d[36:46])
a.Spouse = getBool(d[46:])
return nil
}

func NewBaselineSerializer() goserbench.Serializer {
return &BaselineSerializer{}
}

// BaselineUnsafeSerializer is similar to BaselizeSerializer, but it reuses
// the marshalling buffer and unmarshals unsafe strings.
type BaselineUnsafeSerializer struct {
buf []byte
}

func (b *BaselineUnsafeSerializer) Marshal(o interface{}) ([]byte, error) {
a := o.(*goserbench.SmallStruct)
buf := b.buf
buf = binary.LittleEndian.AppendUint64(buf, uint64(a.BirthDay.UnixNano()))
buf = binary.LittleEndian.AppendUint64(buf, math.Float64bits(a.Money))
buf = binary.LittleEndian.AppendUint32(buf, uint32(a.Siblings))
buf = append(buf, []byte(a.Name)...)
buf = append(buf, []byte(a.Phone)...)
buf = appendBool(buf, a.Spouse)
return buf, nil
}

func (b *BaselineUnsafeSerializer) Unmarshal(d []byte, o interface{}) error {
a := o.(*goserbench.SmallStruct)
a.BirthDay = time.Unix(0, int64(binary.LittleEndian.Uint64(d[:8])))
a.Money = math.Float64frombits(binary.LittleEndian.Uint64(d[8:16]))
Expand All @@ -61,6 +96,8 @@ func (b *BaselineSerializer) Unmarshal(d []byte, o interface{}) error {
return nil
}

func NewBaselineSerializer() goserbench.Serializer {
return &BaselineSerializer{b: make([]byte, 47)}
func NewBaselineUnsafeSerializer() goserbench.Serializer {
return &BaselineUnsafeSerializer{
buf: make([]byte, 0, maxSmallStructSerializeSize),
}
}
7 changes: 7 additions & 0 deletions internal/serializers/bebop_200sc/bebop_200sc.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ func (s *Bebop200ScSerializer) Marshal(o interface{}) (buf []byte, err error) {
a.Siblings = int32(v.Siblings)
a.Spouse = v.Spouse
a.Money = v.Money
if s.buf == nil {
return a.MarshalBebop(), nil
}
n := a.MarshalBebopTo(s.buf)
return s.buf[:n], nil
}
Expand Down Expand Up @@ -50,5 +53,9 @@ func (s *Bebop200ScSerializer) ForceUTC() bool {
}

func NewBebop200ScSerializer() goserbench.Serializer {
return &Bebop200ScSerializer{}
}

func NewBebop200ScReuseSerializer() goserbench.Serializer {
return &Bebop200ScSerializer{buf: make([]byte, 1024)}
}
4 changes: 4 additions & 0 deletions internal/serializers/bebop_wellquite/bebop_wellquite.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ func (s *BebopWellquiteSerializer) TimePrecision() time.Duration {
}

func NewBebopWellquiteSerializer() goserbench.Serializer {
return &BebopWellquiteSerializer{}
}

func NewBebopWellquiteReuseSerializer() goserbench.Serializer {
return &BebopWellquiteSerializer{
buf: make([]byte, 1024),
}
Expand Down
9 changes: 9 additions & 0 deletions internal/serializers/fastjson/fastjson.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,19 @@ func (s *FastJSONSerializer) Unmarshal(bs []byte, o interface{}) (err error) {
}

func NewFastJSONSerializer() goserbench.Serializer {
var arena fastjson.Arena
return &FastJSONSerializer{
object: arena.NewObject(),
arena: arena,
}
}

func NewFastJSONReuseSerializer() goserbench.Serializer {
var arena fastjson.Arena
return &FastJSONSerializer{
object: arena.NewObject(),
arena: arena,
buf: make([]byte, 0, 1024),
}

}
7 changes: 3 additions & 4 deletions internal/serializers/gencode/gencode.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ import (
)

type GencodeSerializer struct {
buf []byte
a GencodeA
a GencodeA
}

func (s *GencodeSerializer) Marshal(o interface{}) ([]byte, error) {
Expand All @@ -20,7 +19,7 @@ func (s *GencodeSerializer) Marshal(o interface{}) ([]byte, error) {
a.Siblings = int32(v.Siblings)
a.Spouse = v.Spouse
a.Money = v.Money
return a.Marshal(s.buf)
return a.Marshal(nil)
}

func (s *GencodeSerializer) Unmarshal(bs []byte, o interface{}) (err error) {
Expand All @@ -41,7 +40,7 @@ func (s *GencodeSerializer) Unmarshal(bs []byte, o interface{}) (err error) {
}

func NewGencodeSerializer() goserbench.Serializer {
return &GencodeSerializer{buf: make([]byte, 0, 1024)}
return &GencodeSerializer{}
}

type GencodeUnsafeSerializer struct {
Expand Down
8 changes: 5 additions & 3 deletions internal/serializers/mus/mus.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,9 @@ func NewMUSSerializer() goserbench.Serializer {
return MUSSerializer{}
}

type MUSUnsafeSerializer struct{}
type MUSUnsafeSerializer struct {
buf []byte
}

func (s MUSUnsafeSerializer) Marshal(o interface{}) ([]byte, error) {
v := o.(*goserbench.SmallStruct)
Expand All @@ -83,7 +85,7 @@ func (s MUSUnsafeSerializer) Marshal(o interface{}) ([]byte, error) {
n += unsafe.SizeInt32(int32(v.Siblings))
n += unsafe.SizeBool(v.Spouse)
n += unsafe.SizeFloat64(v.Money)
buf := make([]byte, n)
buf := s.buf[:n]
n = unsafe.MarshalString(v.Name, buf)
n += unsafe.MarshalInt64(v.BirthDay.UnixNano(), buf[n:])
n += unsafe.MarshalString(v.Phone, buf[n:])
Expand Down Expand Up @@ -132,5 +134,5 @@ func (s MUSUnsafeSerializer) Unmarshal(bs []byte, o interface{}) (err error) {
}

func NewMUSUnsafeSerializer() goserbench.Serializer {
return MUSUnsafeSerializer{}
return MUSUnsafeSerializer{buf: make([]byte, 1024)}
}
Loading

0 comments on commit 4a88092

Please sign in to comment.