From 9a06995460815e05a189a42f224a099dd881502d Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Tue, 31 May 2022 23:10:56 +0300 Subject: [PATCH] bigint: don't allocate in ToPreallocatedBytes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Turns out, it's almost always allocating because we're mostly dealing with small integers while the buffer size is calculated in 8-byte chunks here, so preallocated buffer is always insufficient. name old time/op new time/op delta ToPreallocatedBytes-8 28.5ns ± 7% 19.7ns ± 5% -30.72% (p=0.000 n=10+10) name old alloc/op new alloc/op delta ToPreallocatedBytes-8 16.0B ± 0% 0.0B -100.00% (p=0.000 n=10+10) name old allocs/op new allocs/op delta ToPreallocatedBytes-8 1.00 ± 0% 0.00 -100.00% (p=0.000 n=10+10) Fix StorageItem reuse at the same time. We don't copy when getting values from the storage, but we don when we're putting them, so buffer reuse could corrupt old values. --- pkg/core/dao/dao.go | 10 +++++++++ pkg/core/native/management.go | 3 +-- pkg/core/native/native_neo.go | 7 +++--- pkg/core/native/native_nep17.go | 3 +-- pkg/core/native/oracle.go | 3 +-- pkg/core/native/util.go | 2 +- pkg/encoding/bigint/bench_test.go | 15 +++++++++++++ pkg/encoding/bigint/bigint.go | 37 +++++-------------------------- 8 files changed, 38 insertions(+), 42 deletions(-) create mode 100644 pkg/encoding/bigint/bench_test.go diff --git a/pkg/core/dao/dao.go b/pkg/core/dao/dao.go index 6995612ce..c2563f24d 100644 --- a/pkg/core/dao/dao.go +++ b/pkg/core/dao/dao.go @@ -7,12 +7,14 @@ import ( "errors" "fmt" iocore "io" + "math/big" "sync" "github.com/nspcc-dev/neo-go/pkg/core/block" "github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/storage" "github.com/nspcc-dev/neo-go/pkg/core/transaction" + "github.com/nspcc-dev/neo-go/pkg/encoding/bigint" "github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger" "github.com/nspcc-dev/neo-go/pkg/util" @@ -387,6 +389,14 @@ func (dao *Simple) PutStorageItem(id int32, key []byte, si state.StorageItem) { dao.Store.Put(stKey, si) } +// PutBigInt serializaed and puts the given integer for the given id with the given +// key into the given store. +func (dao *Simple) PutBigInt(id int32, key []byte, n *big.Int) { + var buf [bigint.MaxBytesLen]byte + stData := bigint.ToPreallocatedBytes(n, buf[:]) + dao.PutStorageItem(id, key, stData) +} + // DeleteStorageItem drops a storage item for the given id with the // given key from the store. func (dao *Simple) DeleteStorageItem(id int32, key []byte) { diff --git a/pkg/core/native/management.go b/pkg/core/native/management.go index 92de9015d..b30266624 100644 --- a/pkg/core/native/management.go +++ b/pkg/core/native/management.go @@ -601,8 +601,7 @@ func (m *Management) getNextContractID(d *dao.Simple) (int32, error) { id := bigint.FromBytes(si) ret := int32(id.Int64()) id.Add(id, intOne) - si = bigint.ToPreallocatedBytes(id, si) - d.PutStorageItem(m.ID, keyNextAvailableID, si) + d.PutBigInt(m.ID, keyNextAvailableID, id) return ret, nil } diff --git a/pkg/core/native/native_neo.go b/pkg/core/native/native_neo.go index 7e601d3c8..cde774216 100644 --- a/pkg/core/native/native_neo.go +++ b/pkg/core/native/native_neo.go @@ -440,7 +440,7 @@ func (n *NEO) PostPersist(ic *interop.Context) error { } cache.gasPerVoteCache[cs[i].Key] = *tmp - ic.DAO.PutStorageItem(n.ID, key, bigint.ToBytes(tmp)) + ic.DAO.PutBigInt(n.ID, key, tmp) } } } @@ -1088,8 +1088,7 @@ func (n *NEO) modifyVoterTurnout(d *dao.Simple, amount *big.Int) error { } votersCount := bigint.FromBytes(si) votersCount.Add(votersCount, amount) - si = bigint.ToPreallocatedBytes(votersCount, si) - d.PutStorageItem(n.ID, key, si) + d.PutBigInt(n.ID, key, votersCount) return nil } @@ -1218,5 +1217,5 @@ func (n *NEO) putGASRecord(dao *dao.Simple, index uint32, value *big.Int) { key := make([]byte, 5) key[0] = prefixGASPerBlock binary.BigEndian.PutUint32(key[1:], index) - dao.PutStorageItem(n.ID, key, bigint.ToBytes(value)) + dao.PutBigInt(n.ID, key, value) } diff --git a/pkg/core/native/native_nep17.go b/pkg/core/native/native_nep17.go index e0963530e..6a2c42c02 100644 --- a/pkg/core/native/native_nep17.go +++ b/pkg/core/native/native_nep17.go @@ -108,8 +108,7 @@ func (c *nep17TokenNative) getTotalSupply(d *dao.Simple) (state.StorageItem, *bi } func (c *nep17TokenNative) saveTotalSupply(d *dao.Simple, si state.StorageItem, supply *big.Int) { - si = state.StorageItem(bigint.ToPreallocatedBytes(supply, si)) - d.PutStorageItem(c.ID, totalSupplyKey, si) + d.PutBigInt(c.ID, totalSupplyKey, supply) } func (c *nep17TokenNative) Transfer(ic *interop.Context, args []stackitem.Item) stackitem.Item { diff --git a/pkg/core/native/oracle.go b/pkg/core/native/oracle.go index 0f819104a..505a2680c 100644 --- a/pkg/core/native/oracle.go +++ b/pkg/core/native/oracle.go @@ -355,8 +355,7 @@ func (o *Oracle) RequestInternal(ic *interop.Context, url string, filter *string itemID := bigint.FromBytes(si) id := itemID.Uint64() itemID.Add(itemID, intOne) - si = bigint.ToPreallocatedBytes(itemID, si) - ic.DAO.PutStorageItem(o.ID, prefixRequestID, si) + ic.DAO.PutBigInt(o.ID, prefixRequestID, itemID) // Should be executed from the contract. _, err := ic.GetContract(ic.VM.GetCallingScriptHash()) diff --git a/pkg/core/native/util.go b/pkg/core/native/util.go index a9d85ba9e..0666b9eac 100644 --- a/pkg/core/native/util.go +++ b/pkg/core/native/util.go @@ -37,7 +37,7 @@ func putConvertibleToDAO(id int32, d *dao.Simple, key []byte, conv stackitem.Con } func setIntWithKey(id int32, dao *dao.Simple, key []byte, value int64) { - dao.PutStorageItem(id, key, bigint.ToBytes(big.NewInt(value))) + dao.PutBigInt(id, key, big.NewInt(value)) } func getIntWithKey(id int32, dao *dao.Simple, key []byte) int64 { diff --git a/pkg/encoding/bigint/bench_test.go b/pkg/encoding/bigint/bench_test.go new file mode 100644 index 000000000..411a10b85 --- /dev/null +++ b/pkg/encoding/bigint/bench_test.go @@ -0,0 +1,15 @@ +package bigint + +import ( + "math/big" + "testing" +) + +func BenchmarkToPreallocatedBytes(b *testing.B) { + v := big.NewInt(100500) + buf := make([]byte, 4) + + for i := 0; i < b.N; i++ { + _ = ToPreallocatedBytes(v, buf[:0]) + } +} diff --git a/pkg/encoding/bigint/bigint.go b/pkg/encoding/bigint/bigint.go index 35081992a..ffb11a591 100644 --- a/pkg/encoding/bigint/bigint.go +++ b/pkg/encoding/bigint/bigint.go @@ -1,7 +1,6 @@ package bigint import ( - "encoding/binary" "math/big" "math/bits" @@ -109,36 +108,26 @@ func ToBytes(n *big.Int) []byte { func ToPreallocatedBytes(n *big.Int, data []byte) []byte { sign := n.Sign() if sign == 0 { - return data + return data[:0] } if sign < 0 { n.Add(n, bigOne) defer func() { n.Sub(n, bigOne) }() if n.Sign() == 0 { // n == -1 - return append(data, 0xFF) + return append(data[:0], 0xFF) } } - var ws = n.Bits() + lb := n.BitLen()/8 + 1 - lb := len(ws) * wordSizeBytes if c := cap(data); c < lb { - data = make([]byte, lb, lb+1) + data = make([]byte, lb) } else { data = data[:lb] } - data = wordsToBytes(ws, data) - - size := len(data) - for ; data[size-1] == 0; size-- { - } - - data = data[:size] - - if data[size-1]&0x80 != 0 { - data = append(data, 0) - } + _ = n.FillBytes(data) + slice.Reverse(data) if sign == -1 { for i := range data { @@ -148,17 +137,3 @@ func ToPreallocatedBytes(n *big.Int, data []byte) []byte { return data } - -func wordsToBytes(ws []big.Word, bs []byte) []byte { - if wordSizeBytes == 8 { - for i := range ws { - binary.LittleEndian.PutUint64(bs[i*wordSizeBytes:], uint64(ws[i])) - } - } else { - for i := range ws { - binary.LittleEndian.PutUint32(bs[i*wordSizeBytes:], uint32(ws[i])) - } - } - - return bs -}