17
17
package ledgercore
18
18
19
19
import (
20
+ "github.com/algorand/go-algorand/config"
20
21
"github.com/algorand/go-algorand/data/basics"
21
22
"github.com/algorand/go-algorand/data/bookkeeping"
22
23
"github.com/algorand/go-algorand/data/transactions"
23
24
)
24
25
26
+ const (
27
+ accountArrayEntrySize = uint64 (232 ) // Measured by BenchmarkBalanceRecord
28
+ accountMapCacheEntrySize = uint64 (64 ) // Measured by BenchmarkAcctCache
29
+ txleasesEntrySize = uint64 (112 ) // Measured by BenchmarkTxLeases
30
+ creatablesEntrySize = uint64 (100 ) // Measured by BenchmarkCreatables
31
+ stateDeltaTargetOptimizationThreshold = uint64 (50000000 )
32
+ )
33
+
25
34
// ModifiedCreatable defines the changes to a single single creatable state
26
35
type ModifiedCreatable struct {
27
36
// Type of the creatable: app or asset
@@ -68,6 +77,9 @@ type StateDelta struct {
68
77
69
78
// previous block timestamp
70
79
PrevTimestamp int64
80
+
81
+ // initial hint for allocating data structures for StateDelta
82
+ initialTransactionsCount int
71
83
}
72
84
73
85
// AccountDeltas stores ordered accounts and allows fast lookup by address
@@ -78,18 +90,22 @@ type AccountDeltas struct {
78
90
acctsCache map [basics.Address ]int
79
91
}
80
92
81
- // MakeStateDelta creates a new instance of StateDelta
93
+ // MakeStateDelta creates a new instance of StateDelta.
94
+ // hint is amount of transactions for evaluation, 2 * hint is for sender and receiver balance records.
95
+ // This does not play well for AssetConfig and ApplicationCall transactions on scale
82
96
func MakeStateDelta (hdr * bookkeeping.BlockHeader , prevTimestamp int64 , hint int ) StateDelta {
83
97
return StateDelta {
84
98
Accts : AccountDeltas {
85
99
accts : make ([]basics.BalanceRecord , 0 , hint * 2 ),
86
100
acctsCache : make (map [basics.Address ]int , hint * 2 ),
87
101
},
88
- Txids : make (map [transactions.Txid ]basics.Round , hint ),
89
- Txleases : make (map [Txlease ]basics.Round , hint ),
90
- Creatables : make (map [basics.CreatableIndex ]ModifiedCreatable , hint ),
91
- Hdr : hdr ,
92
- PrevTimestamp : prevTimestamp ,
102
+ Txids : make (map [transactions.Txid ]basics.Round , hint ),
103
+ Txleases : make (map [Txlease ]basics.Round , hint ),
104
+ // asset or application creation are considered as rare events so do not pre-allocate space for them
105
+ Creatables : make (map [basics.CreatableIndex ]ModifiedCreatable ),
106
+ Hdr : hdr ,
107
+ PrevTimestamp : prevTimestamp ,
108
+ initialTransactionsCount : hint ,
93
109
}
94
110
}
95
111
@@ -149,3 +165,44 @@ func (ad *AccountDeltas) upsert(br basics.BalanceRecord) {
149
165
}
150
166
ad .acctsCache [addr ] = last
151
167
}
168
+
169
+ // OptimizeAllocatedMemory by reallocating maps to needed capacity
170
+ // For each data structure, reallocate if it would save us at least 50MB aggregate
171
+ func (sd * StateDelta ) OptimizeAllocatedMemory (proto config.ConsensusParams ) {
172
+ // accts takes up 232 bytes per entry, and is saved for 320 rounds
173
+ if uint64 (cap (sd .Accts .accts )- len (sd .Accts .accts ))* accountArrayEntrySize * proto .MaxBalLookback > stateDeltaTargetOptimizationThreshold {
174
+ accts := make ([]basics.BalanceRecord , len (sd .Accts .acctsCache ))
175
+ copy (accts , sd .Accts .accts )
176
+ sd .Accts .accts = accts
177
+ }
178
+
179
+ // acctsCache takes up 64 bytes per entry, and is saved for 320 rounds
180
+ // realloc if original allocation capacity greater than length of data, and space difference is significant
181
+ if 2 * sd .initialTransactionsCount > len (sd .Accts .acctsCache ) &&
182
+ uint64 (2 * sd .initialTransactionsCount - len (sd .Accts .acctsCache ))* accountMapCacheEntrySize * proto .MaxBalLookback > stateDeltaTargetOptimizationThreshold {
183
+ acctsCache := make (map [basics.Address ]int , len (sd .Accts .acctsCache ))
184
+ for k , v := range sd .Accts .acctsCache {
185
+ acctsCache [k ] = v
186
+ }
187
+ sd .Accts .acctsCache = acctsCache
188
+ }
189
+
190
+ // TxLeases takes up 112 bytes per entry, and is saved for 1000 rounds
191
+ if sd .initialTransactionsCount > len (sd .Txleases ) &&
192
+ uint64 (sd .initialTransactionsCount - len (sd .Txleases ))* txleasesEntrySize * proto .MaxTxnLife > stateDeltaTargetOptimizationThreshold {
193
+ txLeases := make (map [Txlease ]basics.Round , len (sd .Txleases ))
194
+ for k , v := range sd .Txleases {
195
+ txLeases [k ] = v
196
+ }
197
+ sd .Txleases = txLeases
198
+ }
199
+
200
+ // Creatables takes up 100 bytes per entry, and is saved for 320 rounds
201
+ if uint64 (len (sd .Creatables ))* creatablesEntrySize * proto .MaxBalLookback > stateDeltaTargetOptimizationThreshold {
202
+ creatableDeltas := make (map [basics.CreatableIndex ]ModifiedCreatable , len (sd .Creatables ))
203
+ for k , v := range sd .Creatables {
204
+ creatableDeltas [k ] = v
205
+ }
206
+ sd .Creatables = creatableDeltas
207
+ }
208
+ }
0 commit comments