Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

performance: Don't preallocate rarely used maps in MakeStateDelta #4715

Merged
merged 7 commits into from
Nov 3, 2022
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ledger/internal/appcow_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ func getCow(creatables []modsData) *roundCowState {
proto: config.Consensus[protocol.ConsensusCurrentVersion],
}
for _, e := range creatables {
cs.mods.Creatables[e.cidx] = ledgercore.ModifiedCreatable{Ctype: e.ctype, Creator: e.addr, Created: true}
cs.mods.UpsertCreatable(e.cidx, ledgercore.ModifiedCreatable{Ctype: e.ctype, Creator: e.addr, Created: true})
}
return cs
}
Expand Down
26 changes: 16 additions & 10 deletions ledger/internal/assetcow.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,22 +23,28 @@ import (

func (cs *roundCowState) AllocateAsset(addr basics.Address, index basics.AssetIndex, global bool) error {
if global {
cs.mods.Creatables[basics.CreatableIndex(index)] = ledgercore.ModifiedCreatable{
Ctype: basics.AssetCreatable,
Creator: addr,
Created: true,
}
cs.mods.UpsertCreatable(
basics.CreatableIndex(index),
ledgercore.ModifiedCreatable{
Ctype: basics.AssetCreatable,
Creator: addr,
Created: true,
},
)
}
return nil
}

func (cs *roundCowState) DeallocateAsset(addr basics.Address, index basics.AssetIndex, global bool) error {
if global {
cs.mods.Creatables[basics.CreatableIndex(index)] = ledgercore.ModifiedCreatable{
Ctype: basics.AssetCreatable,
Creator: addr,
Created: false,
}
cs.mods.UpsertCreatable(
basics.CreatableIndex(index),
ledgercore.ModifiedCreatable{
Ctype: basics.AssetCreatable,
Creator: addr,
Created: false,
},
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could write a quick test and see if the new code has more allocations than the old one? - wondering if compiler optimizes it right.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you mean for this function in particular? or in general?

I ran the BenchmarkMakeStateDelta that already existed. Out of the box it's the same on master and this branch at 5allocs/op but when adding //go:noinline to MakeStateDelta the difference becomes 7 vs 12 as expected.

That seems odd to me since running go build --gcflags="-m" . in ledger/ledgercore gives this showing that the allocs are escaping to heap when compiled and scenario1 run above confirms it:
Screen Shot 2022-11-03 at 3 27 01 PM

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

then maybe AddCreatable should take a pointer to get rid of additional copying?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like this is already merged but do you want me to add it? This should be called rarely and the objects are tiny so the copy cost is not big but should be safe

}
return nil
}
6 changes: 3 additions & 3 deletions ledger/internal/cow.go
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ func (cb *roundCowState) addTx(txn transactions.Transaction, txid transactions.T
cb.mods.Txids[txid] = ledgercore.IncludedTransactions{LastValid: txn.LastValid, Intra: uint64(len(cb.mods.Txids))}
cb.incTxnCount()
if txn.Lease != [32]byte{} {
cb.mods.Txleases[ledgercore.Txlease{Sender: txn.Sender, Lease: txn.Lease}] = txn.LastValid
cb.mods.UpsertTxLease(ledgercore.Txlease{Sender: txn.Sender, Lease: txn.Lease}, txn.LastValid)
}
}

Expand Down Expand Up @@ -287,10 +287,10 @@ func (cb *roundCowState) commitToParent() {
cb.commitParent.txnCount += cb.txnCount

for txl, expires := range cb.mods.Txleases {
cb.commitParent.mods.Txleases[txl] = expires
cb.commitParent.mods.UpsertTxLease(txl, expires)
}
for cidx, delta := range cb.mods.Creatables {
cb.commitParent.mods.Creatables[cidx] = delta
cb.commitParent.mods.UpsertCreatable(cidx, delta)
}
for addr, smod := range cb.sdeltas {
for aapp, nsd := range smod {
Expand Down
33 changes: 24 additions & 9 deletions ledger/ledgercore/statedelta.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,11 @@ type StateDelta struct {
Txids map[transactions.Txid]IncludedTransactions

// new txleases for the txtail mapped to expiration
// not pre-allocated so use .UpsertTxleases to modify instead of direct assignment
Txleases map[Txlease]basics.Round

// new creatables creator lookup table
// not pre-allocated so use .UpsertCreatables to modify instead of direct assignment
Creatables map[basics.CreatableIndex]ModifiedCreatable

// new block header; read-only
Expand Down Expand Up @@ -180,9 +182,11 @@ type AccountDeltas struct {
// AppResources deltas. If app params or local state is deleted, there is a nil value in AppResources.Params or AppResources.State and Deleted flag set
AppResources []AppResourceRecord
// caches for {addr, app id} to app params delta resolution
// not preallocated - use UpsertAppResource instead of inserting directly
appResourcesCache map[AccountApp]int

AssetResources []AssetResourceRecord
AssetResources []AssetResourceRecord
// not preallocated - use UpsertAssertResource instead of inserting directly
assetResourcesCache map[AccountAsset]int
}

Expand All @@ -191,12 +195,10 @@ type AccountDeltas struct {
// This does not play well for AssetConfig and ApplicationCall transactions on scale
func MakeStateDelta(hdr *bookkeeping.BlockHeader, prevTimestamp int64, hint int, stateProofNext basics.Round) StateDelta {
return StateDelta{
Accts: MakeAccountDeltas(hint),
KvMods: make(map[string]KvValueDelta),
Txids: make(map[transactions.Txid]IncludedTransactions, hint),
Txleases: make(map[Txlease]basics.Round),
Accts: MakeAccountDeltas(hint),
KvMods: make(map[string]KvValueDelta),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

KvMods seems like a candidate for the same treatment. The vast majority of txns will not have any KvMods.

Txids: make(map[transactions.Txid]IncludedTransactions, hint),
// asset or application creation are considered as rare events so do not pre-allocate space for them
Creatables: make(map[basics.CreatableIndex]ModifiedCreatable),
Hdr: hdr,
StateProofNext: stateProofNext,
PrevTimestamp: prevTimestamp,
Expand All @@ -209,9 +211,6 @@ func MakeAccountDeltas(hint int) AccountDeltas {
return AccountDeltas{
Accts: make([]BalanceRecord, 0, hint*2),
acctsCache: make(map[basics.Address]int, hint*2),

appResourcesCache: make(map[AccountApp]int),
assetResourcesCache: make(map[AccountAsset]int),
}
}

Expand Down Expand Up @@ -403,6 +402,22 @@ func (ad *AccountDeltas) UpsertAssetResource(addr basics.Address, aidx basics.As
ad.assetResourcesCache[key] = last
}

// UpsertTxLease adds a new TxLease to the StateDelta
func (sd *StateDelta) UpsertTxLease(txLease Txlease, expired basics.Round) {
iansuvak marked this conversation as resolved.
Show resolved Hide resolved
if sd.Txleases == nil {
sd.Txleases = make(map[Txlease]basics.Round)
}
sd.Txleases[txLease] = expired
}

// UpsertCreatable adds a new Creatable to the StateDelta
func (sd *StateDelta) UpsertCreatable(idx basics.CreatableIndex, creatable ModifiedCreatable) {
if sd.Creatables == nil {
sd.Creatables = make(map[basics.CreatableIndex]ModifiedCreatable)
}
sd.Creatables[idx] = creatable
}

// OptimizeAllocatedMemory by reallocating maps to needed capacity
// For each data structure, reallocate if it would save us at least 50MB aggregate
// If provided maxBalLookback or maxTxnLife are zero, dependent optimizations will not occur.
Expand Down
22 changes: 22 additions & 0 deletions ledger/ledgercore/statedelta_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,28 @@ func TestAccountDeltas(t *testing.T) {
a.Equal(sample1, data)
}

func TestMakeStateDeltaMaps(t *testing.T) {
partitiontest.PartitionTest(t)

sd := MakeStateDelta(nil, 0, 23000, basics.Round(2))
require.Nil(t, sd.Txleases)
require.Nil(t, sd.Creatables)

sd.UpsertTxLease(Txlease{}, basics.Round(10))
require.Equal(t, 1, len(sd.Txleases))
iansuvak marked this conversation as resolved.
Show resolved Hide resolved
sd.UpsertCreatable(basics.CreatableIndex(5), ModifiedCreatable{})
require.Equal(t, 1, len(sd.Creatables))

txLeaseMap := make(map[Txlease]basics.Round)
txLeaseMap[Txlease{}] = basics.Round(10)
require.Equal(t, sd.Txleases, txLeaseMap)

creatableMap := make(map[basics.CreatableIndex]ModifiedCreatable)
creatableMap[basics.CreatableIndex(5)] = ModifiedCreatable{}
require.Equal(t, sd.Creatables, creatableMap)

}

func BenchmarkMakeStateDelta(b *testing.B) {
hint := 23000
b.ReportAllocs()
Expand Down
3 changes: 3 additions & 0 deletions ledger/txtail.go
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,9 @@ func (t *txTail) newBlock(blk bookkeeping.Block, delta ledgercore.StateDelta) {

t.tailMu.Lock()
defer t.tailMu.Unlock()
if delta.Txleases == nil {
delta.Txleases = make(map[ledgercore.Txlease]basics.Round)
}
t.recent[rnd] = roundLeases{
txleases: delta.Txleases,
proto: config.Consensus[blk.CurrentProtocol],
iansuvak marked this conversation as resolved.
Show resolved Hide resolved
Expand Down
2 changes: 1 addition & 1 deletion ledger/txtail_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ func TestTxTailDeltaTracking(t *testing.T) {
LastValid: basics.Round(i + 50),
Intra: 0,
}
deltas.Txleases[ledgercore.Txlease{Sender: blk.Payset[0].Txn.Sender, Lease: blk.Payset[0].Txn.Lease}] = basics.Round(i + 50)
deltas.UpsertTxLease(ledgercore.Txlease{Sender: blk.Payset[0].Txn.Sender, Lease: blk.Payset[0].Txn.Lease}, basics.Round(i+50))

txtail.newBlock(blk, deltas)
txtail.committedUpTo(basics.Round(i))
Expand Down