cosmos-sdk/codec/proto_codec.go
Emmanuel T Odeke cbb68fc6ef
codec: remove unnecessary allocations in ProtoCodec.MarshalBinaryLengthPrefixed (#6877)
* codec: remove unnecessary allocations in ProtoCodec.MarshalBinaryLengthPrefixed

Improve ProtoCodec.MarshalBinaryLengthPrefixed by removing the need
to use a *bytes.Buffer and instead use an array and invoke
binary.PutUvarint, and directly create the respective length prefixed
concatentation.

With this change we get the following improvement in all dimenions
of throughput, bytes allocated, number of allocations per operation.

```shell
$ benchstat before.txt after.txt
name                                     old time/op    new time/op    delta
ProtoCodecMarshalBinaryLengthPrefixed-8     295ns ± 2%     177ns ± 3%  -39.92%  (p=0.000 n=20+20)

name                                     old speed      new speed      delta
ProtoCodecMarshalBinaryLengthPrefixed-8   505MB/s ± 2%   841MB/s ± 3%  +66.44%  (p=0.000 n=20+20)

name                                     old alloc/op   new alloc/op   delta
ProtoCodecMarshalBinaryLengthPrefixed-8      576B ± 0%      336B ± 0%  -41.67%  (p=0.000 n=20+20)

name                                     old allocs/op  new allocs/op  delta
ProtoCodecMarshalBinaryLengthPrefixed-8      5.00 ± 0%      3.00 ± 0%  -40.00%  (p=0.000 n=20+20)
```

Fixes #6875

* Address @marbar3778's feedback

Use binary.MaxVarintLen64 instead of 10

Co-authored-by: Marko <marbar3778@yahoo.com>

Co-authored-by: Marko <marbar3778@yahoo.com>
Co-authored-by: Aaron Craelius <aaron@regen.network>
2020-07-29 14:39:34 +00:00

137 lines
3.1 KiB
Go

package codec
import (
"encoding/binary"
"fmt"
"strings"
"github.com/cosmos/cosmos-sdk/codec/types"
"github.com/gogo/protobuf/jsonpb"
)
// ProtoCodec defines a codec that utilizes Protobuf for both binary and JSON
// encoding.
type ProtoCodec struct {
anyUnpacker types.AnyUnpacker
}
func NewProtoCodec(anyUnpacker types.AnyUnpacker) Marshaler {
return &ProtoCodec{anyUnpacker: anyUnpacker}
}
func (pc *ProtoCodec) MarshalBinaryBare(o ProtoMarshaler) ([]byte, error) {
return o.Marshal()
}
func (pc *ProtoCodec) MustMarshalBinaryBare(o ProtoMarshaler) []byte {
bz, err := pc.MarshalBinaryBare(o)
if err != nil {
panic(err)
}
return bz
}
func (pc *ProtoCodec) MarshalBinaryLengthPrefixed(o ProtoMarshaler) ([]byte, error) {
bz, err := pc.MarshalBinaryBare(o)
if err != nil {
return nil, err
}
var sizeBuf [binary.MaxVarintLen64]byte
n := binary.PutUvarint(sizeBuf[:], uint64(o.Size()))
return append(sizeBuf[:n], bz...), nil
}
func (pc *ProtoCodec) MustMarshalBinaryLengthPrefixed(o ProtoMarshaler) []byte {
bz, err := pc.MarshalBinaryLengthPrefixed(o)
if err != nil {
panic(err)
}
return bz
}
func (pc *ProtoCodec) UnmarshalBinaryBare(bz []byte, ptr ProtoMarshaler) error {
err := ptr.Unmarshal(bz)
if err != nil {
return err
}
err = types.UnpackInterfaces(ptr, pc.anyUnpacker)
if err != nil {
return err
}
return nil
}
func (pc *ProtoCodec) MustUnmarshalBinaryBare(bz []byte, ptr ProtoMarshaler) {
if err := pc.UnmarshalBinaryBare(bz, ptr); err != nil {
panic(err)
}
}
func (pc *ProtoCodec) UnmarshalBinaryLengthPrefixed(bz []byte, ptr ProtoMarshaler) error {
size, n := binary.Uvarint(bz)
if n < 0 {
return fmt.Errorf("invalid number of bytes read from length-prefixed encoding: %d", n)
}
if size > uint64(len(bz)-n) {
return fmt.Errorf("not enough bytes to read; want: %v, got: %v", size, len(bz)-n)
} else if size < uint64(len(bz)-n) {
return fmt.Errorf("too many bytes to read; want: %v, got: %v", size, len(bz)-n)
}
bz = bz[n:]
return pc.UnmarshalBinaryBare(bz, ptr)
}
func (pc *ProtoCodec) MustUnmarshalBinaryLengthPrefixed(bz []byte, ptr ProtoMarshaler) {
if err := pc.UnmarshalBinaryLengthPrefixed(bz, ptr); err != nil {
panic(err)
}
}
func (pc *ProtoCodec) MarshalJSON(o interface{}) ([]byte, error) {
m, ok := o.(ProtoMarshaler)
if !ok {
return nil, fmt.Errorf("cannot protobuf JSON encode unsupported type: %T", o)
}
return ProtoMarshalJSON(m)
}
func (pc *ProtoCodec) MustMarshalJSON(o interface{}) []byte {
bz, err := pc.MarshalJSON(o)
if err != nil {
panic(err)
}
return bz
}
func (pc *ProtoCodec) UnmarshalJSON(bz []byte, ptr interface{}) error {
m, ok := ptr.(ProtoMarshaler)
if !ok {
return fmt.Errorf("cannot protobuf JSON decode unsupported type: %T", ptr)
}
err := jsonpb.Unmarshal(strings.NewReader(string(bz)), m)
if err != nil {
return err
}
return types.UnpackInterfaces(ptr, pc.anyUnpacker)
}
func (pc *ProtoCodec) MustUnmarshalJSON(bz []byte, ptr interface{}) {
if err := pc.UnmarshalJSON(bz, ptr); err != nil {
panic(err)
}
}
func (pc *ProtoCodec) UnpackAny(any *types.Any, iface interface{}) error {
return pc.anyUnpacker.UnpackAny(any, iface)
}