From cbb68fc6ef41c37f392934a6f5be3606260c5ad9 Mon Sep 17 00:00:00 2001 From: Emmanuel T Odeke Date: Wed, 29 Jul 2020 07:39:34 -0700 Subject: [PATCH] codec: remove unnecessary allocations in ProtoCodec.MarshalBinaryLengthPrefixed (#6877) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 Co-authored-by: Marko Co-authored-by: Aaron Craelius --- codec/codec.go | 14 +------------ codec/proto_codec.go | 14 +++---------- codec/proto_codec_test.go | 43 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 24 deletions(-) diff --git a/codec/codec.go b/codec/codec.go index aff90cd3d9..a65231261a 100644 --- a/codec/codec.go +++ b/codec/codec.go @@ -1,12 +1,9 @@ package codec import ( - "encoding/binary" - "io" + "github.com/gogo/protobuf/proto" "github.com/cosmos/cosmos-sdk/codec/types" - - "github.com/gogo/protobuf/proto" ) type ( @@ -58,12 +55,3 @@ type ( Unmarshal(data []byte) error } ) - -func encodeUvarint(w io.Writer, u uint64) (err error) { - var buf [10]byte - - n := binary.PutUvarint(buf[:], u) - _, err = w.Write(buf[0:n]) - - return err -} diff --git a/codec/proto_codec.go b/codec/proto_codec.go index 2a2befc332..09853e7a7c 100644 --- a/codec/proto_codec.go +++ b/codec/proto_codec.go @@ -1,7 +1,6 @@ package codec import ( - "bytes" "encoding/binary" "fmt" "strings" @@ -40,16 +39,9 @@ func (pc *ProtoCodec) MarshalBinaryLengthPrefixed(o ProtoMarshaler) ([]byte, err return nil, err } - buf := new(bytes.Buffer) - if err := encodeUvarint(buf, uint64(o.Size())); err != nil { - return nil, err - } - - if _, err := buf.Write(bz); err != nil { - return nil, err - } - - return buf.Bytes(), nil + 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 { diff --git a/codec/proto_codec_test.go b/codec/proto_codec_test.go index def016a6cf..fd1fc7a235 100644 --- a/codec/proto_codec_test.go +++ b/codec/proto_codec_test.go @@ -5,6 +5,7 @@ import ( "fmt" "testing" + "github.com/gogo/protobuf/proto" "github.com/stretchr/testify/require" "github.com/cosmos/cosmos-sdk/codec" @@ -193,3 +194,45 @@ func TestProtoCodecUnmarshalBinaryLengthPrefixedChecks(t *testing.T) { require.Panics(t, func() { cdc.MustUnmarshalBinaryLengthPrefixed(crafted, recv) }) }) } + +func mustAny(msg proto.Message) *types.Any { + any, err := types.NewAnyWithValue(msg) + if err != nil { + panic(err) + } + return any +} + +func BenchmarkProtoCodecMarshalBinaryLengthPrefixed(b *testing.B) { + var pCdc = codec.NewProtoCodec(types.NewInterfaceRegistry()).(*codec.ProtoCodec) + var msg = &testdata.HasAnimal{ + X: 1000, + Animal: mustAny(&testdata.HasAnimal{ + X: 2000, + Animal: mustAny(&testdata.HasAnimal{ + X: 3000, + Animal: mustAny(&testdata.HasAnimal{ + X: 4000, + Animal: mustAny(&testdata.HasAnimal{ + X: 5000, + Animal: mustAny(&testdata.Cat{ + Moniker: "Garfield", + Lives: 6, + }), + }), + }), + }), + }), + } + + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + blob, err := pCdc.MarshalBinaryLengthPrefixed(msg) + if err != nil { + b.Fatal(err) + } + b.SetBytes(int64(len(blob))) + } +}