Skip to content

Commit 104ca4f

Browse files
Merge pull request #4599 from Build-Squad/boriskozlov/array-of-lists-rebased
Adding an array of state lists
2 parents af0b381 + 21129fe commit 104ca4f

File tree

3 files changed

+148
-188
lines changed

3 files changed

+148
-188
lines changed

module/mempool/herocache/backdata/heropool/linkedlist.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,12 @@ type state struct {
1515
tail EIndex
1616
size uint32
1717
}
18+
19+
// NewStates constructs an array of a doubly linked-lists.
20+
func NewStates(numberOfStates int) []state {
21+
result := make([]state, numberOfStates)
22+
for i := 1; i < numberOfStates; i++ {
23+
result[i] = state{head: InvalidIndex, tail: InvalidIndex, size: 0}
24+
}
25+
return result
26+
}

module/mempool/herocache/backdata/heropool/pool.go

Lines changed: 87 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -18,21 +18,21 @@ const (
1818
NoEjection = EjectionMode("no-ejection")
1919
)
2020

21+
// StateIndex is a type of a state of a placeholder in a pool.
22+
type StateIndex uint
23+
24+
const numberOfStates = 2
25+
const ( // iota is reset to 0
26+
stateFree StateIndex = iota
27+
stateUsed
28+
)
29+
2130
// EIndex is data type representing an entity index in Pool.
2231
type EIndex uint32
2332

2433
// InvalidIndex is used when a link doesnt point anywhere, in other words it is an equivalent of a nil address.
2534
const InvalidIndex EIndex = math.MaxUint32
2635

27-
// A type dedicated to describe possible states of placeholders for entities in the pool.
28-
type StateType string
29-
30-
// A placeholder in a free state can be used to store an entity.
31-
const stateFree StateType = "free-state"
32-
33-
// A placeholder in a used state stores currently an entity.
34-
const stateUsed StateType = "used-state"
35-
3636
// poolEntity represents the data type that is maintained by
3737
type poolEntity struct {
3838
PoolEntity
@@ -64,8 +64,7 @@ func (p PoolEntity) Entity() flow.Entity {
6464

6565
type Pool struct {
6666
logger zerolog.Logger
67-
free state // keeps track of free slots.
68-
used state // keeps track of allocated slots to cachedEntities.
67+
states []state // keeps track of a slot's state
6968
poolEntities []poolEntity
7069
ejectionMode EjectionMode
7170
}
@@ -74,16 +73,8 @@ type Pool struct {
7473
// logger and a provided fixed size.
7574
func NewHeroPool(sizeLimit uint32, ejectionMode EjectionMode, logger zerolog.Logger) *Pool {
7675
l := &Pool{
77-
free: state{
78-
head: InvalidIndex,
79-
tail: InvalidIndex,
80-
size: 0,
81-
},
82-
used: state{
83-
head: InvalidIndex,
84-
tail: InvalidIndex,
85-
size: 0,
86-
},
76+
//construcs states initialized to InvalidIndexes
77+
states: NewStates(numberOfStates),
8778
poolEntities: make([]poolEntity, sizeLimit),
8879
ejectionMode: ejectionMode,
8980
logger: logger,
@@ -105,8 +96,16 @@ func (p *Pool) setDefaultNodeLinkValues() {
10596

10697
// initFreeEntities initializes the free double linked-list with the indices of all cached entity poolEntities.
10798
func (p *Pool) initFreeEntities() {
108-
for i := 0; i < len(p.poolEntities); i++ {
109-
p.appendEntity(stateFree, EIndex(i))
99+
p.states[stateFree].head = 0
100+
p.states[stateFree].tail = 0
101+
p.poolEntities[p.states[stateFree].head].node.prev = InvalidIndex
102+
p.poolEntities[p.states[stateFree].tail].node.next = InvalidIndex
103+
p.states[stateFree].size = 1
104+
for i := 1; i < len(p.poolEntities); i++ {
105+
p.connect(p.states[stateFree].tail, EIndex(i))
106+
p.states[stateFree].tail = EIndex(i)
107+
p.poolEntities[p.states[stateFree].tail].node.next = InvalidIndex
108+
p.states[stateFree].size++
110109
}
111110
}
112111

@@ -124,7 +123,7 @@ func (p *Pool) Add(entityId flow.Identifier, entity flow.Entity, owner uint64) (
124123
p.poolEntities[entityIndex].entity = entity
125124
p.poolEntities[entityIndex].id = entityId
126125
p.poolEntities[entityIndex].owner = owner
127-
p.appendEntity(stateUsed, entityIndex)
126+
p.switchState(stateFree, stateUsed, entityIndex)
128127
}
129128

130129
return entityIndex, slotAvailable, ejectedEntity
@@ -137,10 +136,10 @@ func (p *Pool) Get(entityIndex EIndex) (flow.Identifier, flow.Entity, uint64) {
137136

138137
// All returns all stored entities in this pool.
139138
func (p Pool) All() []PoolEntity {
140-
all := make([]PoolEntity, p.used.size)
141-
next := p.used.head
139+
all := make([]PoolEntity, p.states[stateUsed].size)
140+
next := p.states[stateUsed].head
142141

143-
for i := uint32(0); i < p.used.size; i++ {
142+
for i := uint32(0); i < p.states[stateUsed].size; i++ {
144143
e := p.poolEntities[next]
145144
all[i] = e.PoolEntity
146145
next = e.node.next
@@ -152,14 +151,17 @@ func (p Pool) All() []PoolEntity {
152151
// Head returns the head of used items. Assuming no ejection happened and pool never goes beyond limit, Head returns
153152
// the first inserted element.
154153
func (p Pool) Head() (flow.Entity, bool) {
155-
if p.used.size == 0 {
154+
if p.states[stateUsed].size == 0 {
156155
return nil, false
157156
}
158-
e := p.poolEntities[p.used.head]
157+
e := p.poolEntities[p.states[stateUsed].head]
159158
return e.Entity(), true
160159
}
161160

162161
// sliceIndexForEntity returns a slice index which hosts the next entity to be added to the list.
162+
// This index is invalid if there are no available slots or ejection could not be performed.
163+
// If the valid index is returned then it is guaranteed that it corresponds to a free list head.
164+
// Thus when filled with a new entity a switchState must be applied.
163165
//
164166
// The first boolean return value (hasAvailableSlot) says whether pool has an available slot.
165167
// Pool goes out of available slots if it is full and no ejection is set.
@@ -171,18 +173,18 @@ func (p *Pool) sliceIndexForEntity() (i EIndex, hasAvailableSlot bool, ejectedEn
171173
// LRU ejection
172174
// the used head is the oldest entity, so we turn the used head to a free head here.
173175
invalidatedEntity := p.invalidateUsedHead()
174-
return p.claimFreeHead(), true, invalidatedEntity
176+
return p.states[stateFree].head, true, invalidatedEntity
175177
}
176178

177-
if p.free.size == 0 {
179+
if p.states[stateFree].size == 0 {
178180
// the free list is empty, so we are out of space, and we need to eject.
179181
switch p.ejectionMode {
180182
case NoEjection:
181183
// pool is set for no ejection, hence, no slice index is selected, abort immediately.
182184
return InvalidIndex, false, nil
183185
case RandomEjection:
184186
// we only eject randomly when the pool is full and random ejection is on.
185-
random, err := rand.Uint32n(p.used.size)
187+
random, err := rand.Uint32n(p.states[stateUsed].size)
186188
if err != nil {
187189
p.logger.Fatal().Err(err).
188190
Msg("hero pool random ejection failed - falling back to LRU ejection")
@@ -191,45 +193,44 @@ func (p *Pool) sliceIndexForEntity() (i EIndex, hasAvailableSlot bool, ejectedEn
191193
}
192194
randomIndex := EIndex(random)
193195
invalidatedEntity := p.invalidateEntityAtIndex(randomIndex)
194-
return p.claimFreeHead(), true, invalidatedEntity
196+
return p.states[stateFree].head, true, invalidatedEntity
195197
case LRUEjection:
196198
// LRU ejection
197199
return lruEject()
198200
}
199201
}
200202

201-
// claiming the head of free list as the slice index for the next entity to be added
202-
return p.claimFreeHead(), true, nil
203+
// returning the head of free list as the slice index for the next entity to be added
204+
return p.states[stateFree].head, true, nil
203205
}
204206

205207
// Size returns total number of entities that this list maintains.
206208
func (p Pool) Size() uint32 {
207-
return p.used.size
209+
return p.states[stateUsed].size
208210
}
209211

210212
// getHeads returns entities corresponding to the used and free heads.
211-
func (p *Pool) getHeads() (*poolEntity, *poolEntity) {
213+
func (p Pool) getHeads() (*poolEntity, *poolEntity) {
212214
var usedHead, freeHead *poolEntity
213-
if p.used.size != 0 {
214-
usedHead = &p.poolEntities[p.used.head]
215+
if p.states[stateUsed].size != 0 {
216+
usedHead = &p.poolEntities[p.states[stateUsed].head]
215217
}
216218

217-
if p.free.size != 0 {
218-
freeHead = &p.poolEntities[p.free.head]
219+
if p.states[stateFree].size != 0 {
220+
freeHead = &p.poolEntities[p.states[stateFree].head]
219221
}
220222

221223
return usedHead, freeHead
222224
}
223225

224226
// getTails returns entities corresponding to the used and free tails.
225-
func (p *Pool) getTails() (*poolEntity, *poolEntity) {
227+
func (p Pool) getTails() (*poolEntity, *poolEntity) {
226228
var usedTail, freeTail *poolEntity
227-
if p.used.size != 0 {
228-
usedTail = &p.poolEntities[p.used.tail]
229+
if p.states[stateUsed].size != 0 {
230+
usedTail = &p.poolEntities[p.states[stateUsed].tail]
229231
}
230-
231-
if p.free.size != 0 {
232-
freeTail = &p.poolEntities[p.free.tail]
232+
if p.states[stateFree].size != 0 {
233+
freeTail = &p.poolEntities[p.states[stateFree].tail]
233234
}
234235

235236
return usedTail, freeTail
@@ -245,18 +246,10 @@ func (p *Pool) connect(prev EIndex, next EIndex) {
245246
// also removes the entity the invalidated head is presenting and appends the
246247
// node represented by the used head to the tail of the free list.
247248
func (p *Pool) invalidateUsedHead() flow.Entity {
248-
headSliceIndex := p.used.head
249+
headSliceIndex := p.states[stateUsed].head
249250
return p.invalidateEntityAtIndex(headSliceIndex)
250251
}
251252

252-
// claimFreeHead moves the free head forward, and returns the slice index of the
253-
// old free head to host a new entity.
254-
func (p *Pool) claimFreeHead() EIndex {
255-
oldFreeHeadIndex := p.free.head
256-
p.removeEntity(stateFree, oldFreeHeadIndex)
257-
return oldFreeHeadIndex
258-
}
259-
260253
// Remove removes entity corresponding to given getSliceIndex from the list.
261254
func (p *Pool) Remove(sliceIndex EIndex) flow.Entity {
262255
return p.invalidateEntityAtIndex(sliceIndex)
@@ -270,11 +263,9 @@ func (p *Pool) invalidateEntityAtIndex(sliceIndex EIndex) flow.Entity {
270263
if invalidatedEntity == nil {
271264
panic(fmt.Sprintf("removing an entity from an empty slot with an index : %d", sliceIndex))
272265
}
273-
p.removeEntity(stateUsed, sliceIndex)
266+
p.switchState(stateUsed, stateFree, sliceIndex)
274267
p.poolEntities[sliceIndex].id = flow.ZeroID
275268
p.poolEntities[sliceIndex].entity = nil
276-
p.appendEntity(stateFree, EIndex(sliceIndex))
277-
278269
return invalidatedEntity
279270
}
280271

@@ -292,77 +283,46 @@ func (p *Pool) isInvalidated(sliceIndex EIndex) bool {
292283
return true
293284
}
294285

295-
// utility method that removes an entity from one of the states.
296-
// NOTE: a removed entity has to be added to another state.
297-
func (p *Pool) removeEntity(stateType StateType, entityIndex EIndex) {
298-
var s *state = nil
299-
switch stateType {
300-
case stateFree:
301-
s = &p.free
302-
case stateUsed:
303-
s = &p.used
304-
default:
305-
panic(fmt.Sprintf("unknown state type: %s", stateType))
306-
}
307-
308-
if s.size == 0 {
286+
// switches state of an entity.
287+
func (p *Pool) switchState(stateFrom StateIndex, stateTo StateIndex, entityIndex EIndex) {
288+
// Remove from stateFrom list
289+
if p.states[stateFrom].size == 0 {
309290
panic("Removing an entity from an empty list")
310-
}
311-
if s.size == 1 {
312-
// here set to InvalidIndex
313-
s.head = InvalidIndex
314-
s.tail = InvalidIndex
315-
s.size--
316-
p.poolEntities[entityIndex].node.next = InvalidIndex
317-
p.poolEntities[entityIndex].node.prev = InvalidIndex
318-
return
319-
}
320-
node := p.poolEntities[entityIndex].node
321-
322-
if entityIndex != s.head && entityIndex != s.tail {
323-
// links next and prev elements for non-head and non-tail element
324-
p.connect(node.prev, node.next)
325-
}
326-
327-
if entityIndex == s.head {
328-
// moves head forward
329-
s.head = node.next
330-
p.poolEntities[s.head].node.prev = InvalidIndex
331-
}
291+
} else if p.states[stateFrom].size == 1 {
292+
p.states[stateFrom].head = InvalidIndex
293+
p.states[stateFrom].tail = InvalidIndex
294+
} else {
295+
node := p.poolEntities[entityIndex].node
296+
297+
if entityIndex != p.states[stateFrom].head && entityIndex != p.states[stateFrom].tail {
298+
// links next and prev elements for non-head and non-tail element
299+
p.connect(node.prev, node.next)
300+
}
332301

333-
if entityIndex == s.tail {
334-
// moves tail backwards
335-
s.tail = node.prev
336-
p.poolEntities[s.tail].node.next = InvalidIndex
337-
}
338-
s.size--
339-
p.poolEntities[entityIndex].node.next = InvalidIndex
340-
p.poolEntities[entityIndex].node.prev = InvalidIndex
341-
}
302+
if entityIndex == p.states[stateFrom].head {
303+
// moves head forward
304+
p.states[stateFrom].head = node.next
305+
p.poolEntities[p.states[stateFrom].head].node.prev = InvalidIndex
306+
}
342307

343-
// appends an entity to the tail of the state or creates a first element.
344-
// NOTE: entity should not be in any list before this method is applied
345-
func (p *Pool) appendEntity(stateType StateType, entityIndex EIndex) {
346-
var s *state = nil
347-
switch stateType {
348-
case stateFree:
349-
s = &p.free
350-
case stateUsed:
351-
s = &p.used
352-
default:
353-
panic(fmt.Sprintf("unknown state type: %s", stateType))
308+
if entityIndex == p.states[stateFrom].tail {
309+
// moves tail backwards
310+
p.states[stateFrom].tail = node.prev
311+
p.poolEntities[p.states[stateFrom].tail].node.next = InvalidIndex
312+
}
354313
}
355-
356-
if s.size == 0 {
357-
s.head = entityIndex
358-
s.tail = entityIndex
359-
p.poolEntities[s.head].node.prev = InvalidIndex
360-
p.poolEntities[s.tail].node.next = InvalidIndex
361-
s.size = 1
362-
return
314+
p.states[stateFrom].size--
315+
316+
// Add to stateTo list
317+
if p.states[stateTo].size == 0 {
318+
p.states[stateTo].head = entityIndex
319+
p.states[stateTo].tail = entityIndex
320+
p.poolEntities[p.states[stateTo].head].node.prev = InvalidIndex
321+
p.poolEntities[p.states[stateTo].tail].node.next = InvalidIndex
322+
} else {
323+
p.connect(p.states[stateTo].tail, entityIndex)
324+
p.states[stateTo].tail = entityIndex
325+
p.poolEntities[p.states[stateTo].tail].node.next = InvalidIndex
363326
}
364-
p.connect(s.tail, entityIndex)
365-
s.size++
366-
s.tail = entityIndex
367-
p.poolEntities[s.tail].node.next = InvalidIndex
327+
p.states[stateTo].size++
368328
}

0 commit comments

Comments
 (0)