mirror of
https://github.com/tendermint/tendermint.git
synced 2026-02-12 23:01:30 +00:00
Noticed in profiles that invoking *VoteSignBytes always created a bytes.Buffer, then discarded it inside protoio.MarshalDelimited. I dug further and examined the call paths and noticed that we unconditionally create the bytes.Buffer, even though we might have proto messages (in the common case) that implement MarshalTo([]byte), and invoked varintWriter. Instead by inlining this case, we skip a bunch of allocations and CPU cycles, which then reflects properly on all calling functions. Here are the benchmark results: ```shell $ benchstat before.txt after.txt name old time/op new time/op delta types.VoteSignBytes-8 705ns ± 3% 573ns ± 6% -18.74% (p=0.000 n=18+20) types.CommitVoteSignBytes-8 8.15µs ± 9% 6.81µs ± 4% -16.51% (p=0.000 n=20+19) protoio.MarshalDelimitedWithMarshalTo-8 788ns ± 8% 772ns ± 3% -2.01% (p=0.050 n=20+20) protoio.MarshalDelimitedNoMarshalTo-8 989ns ± 4% 845ns ± 2% -14.51% (p=0.000 n=20+18) name old alloc/op new alloc/op delta types.VoteSignBytes-8 792B ± 0% 600B ± 0% -24.24% (p=0.000 n=20+20) types.CommitVoteSignBytes-8 9.52kB ± 0% 7.60kB ± 0% -20.17% (p=0.000 n=20+20) protoio.MarshalDelimitedNoMarshalTo-8 808B ± 0% 440B ± 0% -45.54% (p=0.000 n=20+20) name old allocs/op new allocs/op delta types.VoteSignBytes-8 13.0 ± 0% 10.0 ± 0% -23.08% (p=0.000 n=20+20) types.CommitVoteSignBytes-8 140 ± 0% 110 ± 0% -21.43% (p=0.000 n=20+20) protoio.MarshalDelimitedNoMarshalTo-8 10.0 ± 0% 7.0 ± 0% -30.00% (p=0.000 n=20+20) ``` Thanks to Tharsis who tasked me to help them increase TPS and who are keen on improving Tendermint and efficiency.
92 lines
2.0 KiB
Go
92 lines
2.0 KiB
Go
package protoio_test
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/gogo/protobuf/proto"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/tendermint/tendermint/crypto"
|
|
"github.com/tendermint/tendermint/crypto/tmhash"
|
|
"github.com/tendermint/tendermint/internal/libs/protoio"
|
|
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
|
|
"github.com/tendermint/tendermint/types"
|
|
)
|
|
|
|
func aVote() *types.Vote {
|
|
var stamp, err = time.Parse(types.TimeFormat, "2017-12-25T03:00:01.234Z")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
return &types.Vote{
|
|
Type: tmproto.SignedMsgType(byte(tmproto.PrevoteType)),
|
|
Height: 12345,
|
|
Round: 2,
|
|
Timestamp: stamp,
|
|
BlockID: types.BlockID{
|
|
Hash: tmhash.Sum([]byte("blockID_hash")),
|
|
PartSetHeader: types.PartSetHeader{
|
|
Total: 1000000,
|
|
Hash: tmhash.Sum([]byte("blockID_part_set_header_hash")),
|
|
},
|
|
},
|
|
ValidatorAddress: crypto.AddressHash([]byte("validator_address")),
|
|
ValidatorIndex: 56789,
|
|
}
|
|
}
|
|
|
|
type excludedMarshalTo struct {
|
|
msg proto.Message
|
|
}
|
|
|
|
func (emt *excludedMarshalTo) ProtoMessage() {}
|
|
func (emt *excludedMarshalTo) String() string {
|
|
return emt.msg.String()
|
|
}
|
|
func (emt *excludedMarshalTo) Reset() {
|
|
emt.msg.Reset()
|
|
}
|
|
func (emt *excludedMarshalTo) Marshal() ([]byte, error) {
|
|
return proto.Marshal(emt.msg)
|
|
}
|
|
|
|
var _ proto.Message = (*excludedMarshalTo)(nil)
|
|
|
|
var sink interface{}
|
|
|
|
func BenchmarkMarshalDelimitedWithMarshalTo(b *testing.B) {
|
|
msgs := []proto.Message{
|
|
aVote().ToProto(),
|
|
}
|
|
benchmarkMarshalDelimited(b, msgs)
|
|
}
|
|
|
|
func BenchmarkMarshalDelimitedNoMarshalTo(b *testing.B) {
|
|
msgs := []proto.Message{
|
|
&excludedMarshalTo{aVote().ToProto()},
|
|
}
|
|
benchmarkMarshalDelimited(b, msgs)
|
|
}
|
|
|
|
func benchmarkMarshalDelimited(b *testing.B, msgs []proto.Message) {
|
|
b.ResetTimer()
|
|
b.ReportAllocs()
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
for _, msg := range msgs {
|
|
blob, err := protoio.MarshalDelimited(msg)
|
|
require.Nil(b, err)
|
|
sink = blob
|
|
}
|
|
}
|
|
|
|
if sink == nil {
|
|
b.Fatal("Benchmark did not run")
|
|
}
|
|
|
|
// Reset the sink.
|
|
sink = (interface{})(nil)
|
|
}
|