diff --git a/store/rootmulti/internal/maps/bench_test.go b/store/rootmulti/internal/maps/bench_test.go new file mode 100644 index 000000000..4d7f680c7 --- /dev/null +++ b/store/rootmulti/internal/maps/bench_test.go @@ -0,0 +1,13 @@ +package maps + +import "testing" + +func BenchmarkKVPairBytes(b *testing.B) { + kvp := NewKVPair(make([]byte, 128), make([]byte, 1e6)) + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + b.SetBytes(int64(len(kvp.Bytes()))) + } +} diff --git a/store/rootmulti/internal/maps/maps.go b/store/rootmulti/internal/maps/maps.go index 91edd1177..1dfdca6ff 100644 --- a/store/rootmulti/internal/maps/maps.go +++ b/store/rootmulti/internal/maps/maps.go @@ -1,9 +1,7 @@ package maps import ( - "bytes" "encoding/binary" - "io" "github.com/tendermint/tendermint/crypto/merkle" "github.com/tendermint/tendermint/crypto/tmhash" @@ -55,47 +53,12 @@ func (sm *merkleMap) sort() { sm.sorted = true } -// kvPair defines a type alias for kv.Pair so that we can create bytes to hash -// when constructing the merkle root. Note, key and values are both length-prefixed. -type kvPair kv.Pair - -// bytes returns a byte slice representation of the kvPair where the key and value -// are length-prefixed. -func (kv kvPair) bytes() []byte { - var b bytes.Buffer - - err := encodeByteSlice(&b, kv.Key) - if err != nil { - panic(err) - } - - err = encodeByteSlice(&b, kv.Value) - if err != nil { - panic(err) - } - - return b.Bytes() -} - -func encodeByteSlice(w io.Writer, bz []byte) error { - var buf [8]byte - n := binary.PutUvarint(buf[:], uint64(len(bz))) - - _, err := w.Write(buf[:n]) - if err != nil { - return err - } - - _, err = w.Write(bz) - return err -} - // hashKVPairs hashes a kvPair and creates a merkle tree where the leaves are // byte slices. func hashKVPairs(kvs kv.Pairs) []byte { kvsH := make([][]byte, len(kvs)) for i, kvp := range kvs { - kvsH[i] = kvPair(kvp).bytes() + kvsH[i] = KVPair(kvp).Bytes() } return merkle.SimpleHashFromByteSlices(kvsH) @@ -177,16 +140,23 @@ func NewKVPair(key, value []byte) KVPair { // Bytes returns key || value, with both the // key and value length prefixed. func (kv KVPair) Bytes() []byte { - var b bytes.Buffer - err := encodeByteSlice(&b, kv.Key) - if err != nil { - panic(err) - } - err = encodeByteSlice(&b, kv.Value) - if err != nil { - panic(err) - } - return b.Bytes() + // In the worst case: + // * 8 bytes to Uvarint encode the length of the key + // * 8 bytes to Uvarint encode the length of the value + // So preallocate for the worst case, which will in total + // be a maximum of 14 bytes wasted, if len(key)=1, len(value)=1, + // but that's going to rare. + buf := make([]byte, 8+len(kv.Key)+8+len(kv.Value)) + + // Encode the key, prefixed with its length. + nlk := binary.PutUvarint(buf, uint64(len(kv.Key))) + nk := copy(buf[nlk:], kv.Key) + + // Encode the value, prefixing with its length. + nlv := binary.PutUvarint(buf[nlk+nk:], uint64(len(kv.Value))) + nv := copy(buf[nlk+nk+nlv:], kv.Value) + + return buf[:nlk+nk+nlv+nv] } // SimpleHashFromMap computes a merkle tree from sorted map and returns the merkle