From 6c6136d5512f89ee99c796fb74757052dd6da79e Mon Sep 17 00:00:00 2001 From: Dan Laine Date: Tue, 23 Jun 2020 16:44:02 -0400 Subject: [PATCH] only downsize underlying arrays if they're much too large --- database/common.go | 14 ++++++++++++++ database/encdb/db.go | 10 +++++----- database/memdb/db.go | 9 ++++++--- database/prefixdb/db.go | 10 +++++----- database/rpcdb/db_client.go | 10 +++++----- database/versiondb/db.go | 6 +++++- ids/short_set.go | 4 ++-- snow/validators/set.go | 35 +++++++++++++++++++++++++++++++++-- vms/platformvm/vm.go | 2 +- 9 files changed, 76 insertions(+), 24 deletions(-) create mode 100644 database/common.go diff --git a/database/common.go b/database/common.go new file mode 100644 index 0000000..26b0531 --- /dev/null +++ b/database/common.go @@ -0,0 +1,14 @@ +package database + +const ( + // MaxExcessCapacityFactor ... + // If, when a batch is reset, the cap(batch)/len(batch) > MaxExcessCapacityFactor, + // the underlying array's capacity will be reduced by a factor of capacityReductionFactor. + // Higher value for MaxExcessCapacityFactor --> less aggressive array downsizing --> less memory allocations + // but more unnecessary data in the underlying array that can't be garbage collected. + // Higher value for CapacityReductionFactor --> more aggressive array downsizing --> more memory allocations + // but less unnecessary data in the underlying array that can't be garbage collected. + MaxExcessCapacityFactor = 4 + // CapacityReductionFactor ... + CapacityReductionFactor = 2 +) diff --git a/database/encdb/db.go b/database/encdb/db.go index ddf47e0..8f0d8e3 100644 --- a/database/encdb/db.go +++ b/database/encdb/db.go @@ -17,10 +17,6 @@ import ( "github.com/ava-labs/gecko/utils/hashing" ) -const ( - minBatchSize = 32 -) - // Database encrypts all values that are provided type Database struct { lock sync.RWMutex @@ -205,7 +201,11 @@ func (b *batch) Write() error { // Reset resets the batch for reuse. func (b *batch) Reset() { - b.writes = make([]keyValue, 0, minBatchSize) + if cap(b.writes) > len(b.writes)*database.MaxExcessCapacityFactor { + b.writes = make([]keyValue, 0, cap(b.writes)/database.CapacityReductionFactor) + } else { + b.writes = b.writes[:0] + } b.Batch.Reset() } diff --git a/database/memdb/db.go b/database/memdb/db.go index 5bbd3a2..94ba395 100644 --- a/database/memdb/db.go +++ b/database/memdb/db.go @@ -15,8 +15,7 @@ import ( const ( // DefaultSize is the default initial size of the memory database - DefaultSize = 1 << 10 - minBatchSize = 32 + DefaultSize = 1 << 10 ) // Database is an ephemeral key-value store that implements the Database @@ -194,7 +193,11 @@ func (b *batch) Write() error { // Reset implements the Batch interface func (b *batch) Reset() { - b.writes = make([]keyValue, 0, minBatchSize) + if cap(b.writes) > len(b.writes)*database.MaxExcessCapacityFactor { + b.writes = make([]keyValue, 0, cap(b.writes)/database.CapacityReductionFactor) + } else { + b.writes = b.writes[:0] + } b.size = 0 } diff --git a/database/prefixdb/db.go b/database/prefixdb/db.go index a413846..7f606b2 100644 --- a/database/prefixdb/db.go +++ b/database/prefixdb/db.go @@ -12,10 +12,6 @@ import ( "github.com/ava-labs/gecko/utils/hashing" ) -const ( - minBatchSize = 32 -) - // Database partitions a database into a sub-database by prefixing all keys with // a unique value. type Database struct { @@ -203,7 +199,11 @@ func (b *batch) Write() error { // Reset resets the batch for reuse. func (b *batch) Reset() { - b.writes = make([]keyValue, 0, minBatchSize) + if cap(b.writes) > len(b.writes)*database.MaxExcessCapacityFactor { + b.writes = make([]keyValue, 0, cap(b.writes)/database.CapacityReductionFactor) + } else { + b.writes = b.writes[:0] + } b.Batch.Reset() } diff --git a/database/rpcdb/db_client.go b/database/rpcdb/db_client.go index f1a3abc..401e404 100644 --- a/database/rpcdb/db_client.go +++ b/database/rpcdb/db_client.go @@ -14,10 +14,6 @@ import ( "github.com/ava-labs/gecko/utils" ) -const ( - minBatchSize = 32 -) - var ( errClosed = fmt.Sprintf("rpc error: code = Unknown desc = %s", database.ErrClosed) errNotFound = fmt.Sprintf("rpc error: code = Unknown desc = %s", database.ErrNotFound) @@ -184,7 +180,11 @@ func (b *batch) Write() error { } func (b *batch) Reset() { - b.writes = make([]keyValue, 0, minBatchSize) + if cap(b.writes) > len(b.writes)*database.MaxExcessCapacityFactor { + b.writes = make([]keyValue, 0, cap(b.writes)/database.CapacityReductionFactor) + } else { + b.writes = b.writes[:0] + } b.size = 0 } diff --git a/database/versiondb/db.go b/database/versiondb/db.go index 050de0a..a1f9a18 100644 --- a/database/versiondb/db.go +++ b/database/versiondb/db.go @@ -305,7 +305,11 @@ func (b *batch) Write() error { // Reset implements the Database interface func (b *batch) Reset() { - b.writes = make([]keyValue, 0) + if cap(b.writes) > len(b.writes)*database.MaxExcessCapacityFactor { + b.writes = make([]keyValue, 0, cap(b.writes)/database.CapacityReductionFactor) + } else { + b.writes = b.writes[:0] + } b.size = 0 } diff --git a/ids/short_set.go b/ids/short_set.go index 90766cd..9bcd37d 100644 --- a/ids/short_set.go +++ b/ids/short_set.go @@ -58,10 +58,10 @@ func (ids *ShortSet) Remove(idList ...ShortID) { func (ids *ShortSet) Clear() { *ids = nil } // CappedList returns a list of length at most [size]. -// Size should be >= 0. If size < 0, returns empty list. +// Size should be >= 0. If size < 0, returns nil. func (ids ShortSet) CappedList(size int) []ShortID { if size < 0 { - return make([]ShortID, 0, 0) + return nil } if l := ids.Len(); l < size { size = l diff --git a/snow/validators/set.go b/snow/validators/set.go index c33395f..4fddf98 100644 --- a/snow/validators/set.go +++ b/snow/validators/set.go @@ -13,6 +13,19 @@ import ( "github.com/ava-labs/gecko/utils/random" ) +const ( + // maxExcessCapacityFactor ... + // If, when the validator set is reset, cap(set)/len(set) > MaxExcessCapacityFactor, + // the underlying arrays' capacities will be reduced by a factor of capacityReductionFactor. + // Higher value for maxExcessCapacityFactor --> less aggressive array downsizing --> less memory allocations + // but more unnecessary data in the underlying array that can't be garbage collected. + // Higher value for capacityReductionFactor --> more aggressive array downsizing --> more memory allocations + // but less unnecessary data in the underlying array that can't be garbage collected. + maxExcessCapacityFactor = 4 + // CapacityReductionFactor ... + capacityReductionFactor = 2 +) + // Set of validators that can be sampled type Set interface { fmt.Stringer @@ -72,9 +85,27 @@ func (s *set) Set(vdrs []Validator) { func (s *set) set(vdrs []Validator) { lenVdrs := len(vdrs) + // If the underlying arrays are much larger than necessary, resize them to + // allow garbage collection of unused memory + if cap(s.vdrSlice) > len(s.vdrSlice)*maxExcessCapacityFactor { + newCap := cap(s.vdrSlice) / capacityReductionFactor + if newCap < lenVdrs { + newCap = lenVdrs + } + s.vdrSlice = make([]Validator, 0, newCap) + } else { + s.vdrSlice = s.vdrSlice[:0] + } + if cap(s.sampler.Weights) > len(s.sampler.Weights)*maxExcessCapacityFactor { + newCap := cap(s.sampler.Weights) / capacityReductionFactor + if newCap < lenVdrs { + newCap = lenVdrs + } + s.sampler.Weights = make([]uint64, 0, newCap) + } else { + s.sampler.Weights = s.sampler.Weights[:0] + } s.vdrMap = make(map[[20]byte]int, lenVdrs) - s.vdrSlice = make([]Validator, 0, lenVdrs) - s.sampler.Weights = make([]uint64, 0, lenVdrs) for _, vdr := range vdrs { s.add(vdr) diff --git a/vms/platformvm/vm.go b/vms/platformvm/vm.go index 01bb6a4..8b9350f 100644 --- a/vms/platformvm/vm.go +++ b/vms/platformvm/vm.go @@ -808,7 +808,7 @@ func (vm *VM) getValidators(validatorEvents *EventHeap) []validators.Validator { validator.Wght = weight } - vdrList := make([]validators.Validator, len(vdrMap), len(vdrMap)) + vdrList := make([]validators.Validator, len(vdrMap)) i := 0 for _, validator := range vdrMap { vdrList[i] = validator