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:
parent
c3d989ebda
commit
9a06995460
8 changed files with 38 additions and 42 deletions
|
@ -7,12 +7,14 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
iocore "io"
|
iocore "io"
|
||||||
|
"math/big"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
"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/state"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/storage"
|
"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/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/io"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"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)
|
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
|
// DeleteStorageItem drops a storage item for the given id with the
|
||||||
// given key from the store.
|
// given key from the store.
|
||||||
func (dao *Simple) DeleteStorageItem(id int32, key []byte) {
|
func (dao *Simple) DeleteStorageItem(id int32, key []byte) {
|
||||||
|
|
|
@ -601,8 +601,7 @@ func (m *Management) getNextContractID(d *dao.Simple) (int32, error) {
|
||||||
id := bigint.FromBytes(si)
|
id := bigint.FromBytes(si)
|
||||||
ret := int32(id.Int64())
|
ret := int32(id.Int64())
|
||||||
id.Add(id, intOne)
|
id.Add(id, intOne)
|
||||||
si = bigint.ToPreallocatedBytes(id, si)
|
d.PutBigInt(m.ID, keyNextAvailableID, id)
|
||||||
d.PutStorageItem(m.ID, keyNextAvailableID, si)
|
|
||||||
return ret, nil
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -440,7 +440,7 @@ func (n *NEO) PostPersist(ic *interop.Context) error {
|
||||||
}
|
}
|
||||||
cache.gasPerVoteCache[cs[i].Key] = *tmp
|
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 := bigint.FromBytes(si)
|
||||||
votersCount.Add(votersCount, amount)
|
votersCount.Add(votersCount, amount)
|
||||||
si = bigint.ToPreallocatedBytes(votersCount, si)
|
d.PutBigInt(n.ID, key, votersCount)
|
||||||
d.PutStorageItem(n.ID, key, si)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1218,5 +1217,5 @@ func (n *NEO) putGASRecord(dao *dao.Simple, index uint32, value *big.Int) {
|
||||||
key := make([]byte, 5)
|
key := make([]byte, 5)
|
||||||
key[0] = prefixGASPerBlock
|
key[0] = prefixGASPerBlock
|
||||||
binary.BigEndian.PutUint32(key[1:], index)
|
binary.BigEndian.PutUint32(key[1:], index)
|
||||||
dao.PutStorageItem(n.ID, key, bigint.ToBytes(value))
|
dao.PutBigInt(n.ID, key, value)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
func (c *nep17TokenNative) saveTotalSupply(d *dao.Simple, si state.StorageItem, supply *big.Int) {
|
||||||
si = state.StorageItem(bigint.ToPreallocatedBytes(supply, si))
|
d.PutBigInt(c.ID, totalSupplyKey, supply)
|
||||||
d.PutStorageItem(c.ID, totalSupplyKey, si)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *nep17TokenNative) Transfer(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
func (c *nep17TokenNative) Transfer(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||||
|
|
|
@ -355,8 +355,7 @@ func (o *Oracle) RequestInternal(ic *interop.Context, url string, filter *string
|
||||||
itemID := bigint.FromBytes(si)
|
itemID := bigint.FromBytes(si)
|
||||||
id := itemID.Uint64()
|
id := itemID.Uint64()
|
||||||
itemID.Add(itemID, intOne)
|
itemID.Add(itemID, intOne)
|
||||||
si = bigint.ToPreallocatedBytes(itemID, si)
|
ic.DAO.PutBigInt(o.ID, prefixRequestID, itemID)
|
||||||
ic.DAO.PutStorageItem(o.ID, prefixRequestID, si)
|
|
||||||
|
|
||||||
// Should be executed from the contract.
|
// Should be executed from the contract.
|
||||||
_, err := ic.GetContract(ic.VM.GetCallingScriptHash())
|
_, err := ic.GetContract(ic.VM.GetCallingScriptHash())
|
||||||
|
|
|
@ -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) {
|
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 {
|
func getIntWithKey(id int32, dao *dao.Simple, key []byte) int64 {
|
||||||
|
|
15
pkg/encoding/bigint/bench_test.go
Normal file
15
pkg/encoding/bigint/bench_test.go
Normal 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])
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,6 @@
|
||||||
package bigint
|
package bigint
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
|
||||||
"math/big"
|
"math/big"
|
||||||
"math/bits"
|
"math/bits"
|
||||||
|
|
||||||
|
@ -109,36 +108,26 @@ func ToBytes(n *big.Int) []byte {
|
||||||
func ToPreallocatedBytes(n *big.Int, data []byte) []byte {
|
func ToPreallocatedBytes(n *big.Int, data []byte) []byte {
|
||||||
sign := n.Sign()
|
sign := n.Sign()
|
||||||
if sign == 0 {
|
if sign == 0 {
|
||||||
return data
|
return data[:0]
|
||||||
}
|
}
|
||||||
|
|
||||||
if sign < 0 {
|
if sign < 0 {
|
||||||
n.Add(n, bigOne)
|
n.Add(n, bigOne)
|
||||||
defer func() { n.Sub(n, bigOne) }()
|
defer func() { n.Sub(n, bigOne) }()
|
||||||
if n.Sign() == 0 { // n == -1
|
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 {
|
if c := cap(data); c < lb {
|
||||||
data = make([]byte, lb, lb+1)
|
data = make([]byte, lb)
|
||||||
} else {
|
} else {
|
||||||
data = data[:lb]
|
data = data[:lb]
|
||||||
}
|
}
|
||||||
data = wordsToBytes(ws, data)
|
_ = n.FillBytes(data)
|
||||||
|
slice.Reverse(data)
|
||||||
size := len(data)
|
|
||||||
for ; data[size-1] == 0; size-- {
|
|
||||||
}
|
|
||||||
|
|
||||||
data = data[:size]
|
|
||||||
|
|
||||||
if data[size-1]&0x80 != 0 {
|
|
||||||
data = append(data, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
if sign == -1 {
|
if sign == -1 {
|
||||||
for i := range data {
|
for i := range data {
|
||||||
|
@ -148,17 +137,3 @@ func ToPreallocatedBytes(n *big.Int, data []byte) []byte {
|
||||||
|
|
||||||
return data
|
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
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue