bigint: don't allocate in ToPreallocatedBytes

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.
This commit is contained in:
Roman Khimov 2022-05-31 23:10:56 +03:00
parent c3d989ebda
commit 9a06995460
8 changed files with 38 additions and 42 deletions

View file

@ -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) {

View file

@ -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
}

View file

@ -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)
}

View file

@ -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 {

View file

@ -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())

View file

@ -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 {

View file

@ -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])
}
}

View file

@ -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
}