Skip to content

Commit a95af3e

Browse files
authoredFeb 15, 2021
Merge pull request #75 from bluele/lfu-remove-empty-entry
lfu: remove an empty `freqEntry` in `freqList`
2 parents 610497d + 38c8006 commit a95af3e

File tree

2 files changed

+158
-21
lines changed

2 files changed

+158
-21
lines changed
 

‎lfu.go

+47-21
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,21 @@ type LFUCache struct {
1212
freqList *list.List // list for freqEntry
1313
}
1414

15+
var _ Cache = (*LFUCache)(nil)
16+
17+
type lfuItem struct {
18+
clock Clock
19+
key interface{}
20+
value interface{}
21+
freqElement *list.Element
22+
expiration *time.Time
23+
}
24+
25+
type freqEntry struct {
26+
freq uint
27+
items map[*lfuItem]struct{}
28+
}
29+
1530
func newLFUCache(cb *CacheBuilder) *LFUCache {
1631
c := &LFUCache{}
1732
buildCache(&c.baseCache, cb)
@@ -23,7 +38,7 @@ func newLFUCache(cb *CacheBuilder) *LFUCache {
2338

2439
func (c *LFUCache) init() {
2540
c.freqList = list.New()
26-
c.items = make(map[interface{}]*lfuItem, c.size+1)
41+
c.items = make(map[interface{}]*lfuItem, c.size)
2742
c.freqList.PushFront(&freqEntry{
2843
freq: 0,
2944
items: make(map[*lfuItem]struct{}),
@@ -183,12 +198,28 @@ func (c *LFUCache) increment(item *lfuItem) {
183198
nextFreq := currentFreqEntry.freq + 1
184199
delete(currentFreqEntry.items, item)
185200

201+
// a boolean whether reuse the empty current entry
202+
removable := isRemovableFreqEntry(currentFreqEntry)
203+
204+
// insert item into a valid entry
186205
nextFreqElement := currentFreqElement.Next()
187-
if nextFreqElement == nil {
188-
nextFreqElement = c.freqList.InsertAfter(&freqEntry{
189-
freq: nextFreq,
190-
items: make(map[*lfuItem]struct{}),
191-
}, currentFreqElement)
206+
switch {
207+
case nextFreqElement == nil || nextFreqElement.Value.(*freqEntry).freq > nextFreq:
208+
if removable {
209+
currentFreqEntry.freq = nextFreq
210+
nextFreqElement = currentFreqElement
211+
} else {
212+
nextFreqElement = c.freqList.InsertAfter(&freqEntry{
213+
freq: nextFreq,
214+
items: make(map[*lfuItem]struct{}),
215+
}, currentFreqElement)
216+
}
217+
case nextFreqElement.Value.(*freqEntry).freq == nextFreq:
218+
if removable {
219+
c.freqList.Remove(currentFreqElement)
220+
}
221+
default:
222+
panic("unreachable")
192223
}
193224
nextFreqElement.Value.(*freqEntry).items[item] = struct{}{}
194225
item.freqElement = nextFreqElement
@@ -201,7 +232,7 @@ func (c *LFUCache) evict(count int) {
201232
if entry == nil {
202233
return
203234
} else {
204-
for item, _ := range entry.Value.(*freqEntry).items {
235+
for item := range entry.Value.(*freqEntry).items {
205236
if i >= count {
206237
return
207238
}
@@ -247,8 +278,12 @@ func (c *LFUCache) remove(key interface{}) bool {
247278

248279
// removeElement is used to remove a given list element from the cache
249280
func (c *LFUCache) removeItem(item *lfuItem) {
281+
entry := item.freqElement.Value.(*freqEntry)
250282
delete(c.items, item.key)
251-
delete(item.freqElement.Value.(*freqEntry).items, item)
283+
delete(entry.items, item)
284+
if isRemovableFreqEntry(entry) {
285+
c.freqList.Remove(item.freqElement)
286+
}
252287
if c.evictedFunc != nil {
253288
c.evictedFunc(item.key, item.value)
254289
}
@@ -325,19 +360,6 @@ func (c *LFUCache) Purge() {
325360
c.init()
326361
}
327362

328-
type freqEntry struct {
329-
freq uint
330-
items map[*lfuItem]struct{}
331-
}
332-
333-
type lfuItem struct {
334-
clock Clock
335-
key interface{}
336-
value interface{}
337-
freqElement *list.Element
338-
expiration *time.Time
339-
}
340-
341363
// IsExpired returns boolean value whether this item is expired or not.
342364
func (it *lfuItem) IsExpired(now *time.Time) bool {
343365
if it.expiration == nil {
@@ -349,3 +371,7 @@ func (it *lfuItem) IsExpired(now *time.Time) bool {
349371
}
350372
return it.expiration.Before(*now)
351373
}
374+
375+
func isRemovableFreqEntry(entry *freqEntry) bool {
376+
return entry.freq != 0 && len(entry.items) == 0
377+
}

‎lfu_test.go

+111
Original file line numberDiff line numberDiff line change
@@ -83,3 +83,114 @@ func TestLFUHas(t *testing.T) {
8383
})
8484
}
8585
}
86+
87+
func TestLFUFreqListOrder(t *testing.T) {
88+
gc := buildTestCache(t, TYPE_LFU, 5)
89+
for i := 4; i >= 0; i-- {
90+
gc.Set(i, i)
91+
for j := 0; j <= i; j++ {
92+
gc.Get(i)
93+
}
94+
}
95+
if l := gc.(*LFUCache).freqList.Len(); l != 6 {
96+
t.Fatalf("%v != 6", l)
97+
}
98+
var i uint
99+
for e := gc.(*LFUCache).freqList.Front(); e != nil; e = e.Next() {
100+
if e.Value.(*freqEntry).freq != i {
101+
t.Fatalf("%v != %v", e.Value.(*freqEntry).freq, i)
102+
}
103+
i++
104+
}
105+
gc.Remove(1)
106+
107+
if l := gc.(*LFUCache).freqList.Len(); l != 5 {
108+
t.Fatalf("%v != 5", l)
109+
}
110+
gc.Set(1, 1)
111+
if l := gc.(*LFUCache).freqList.Len(); l != 5 {
112+
t.Fatalf("%v != 5", l)
113+
}
114+
gc.Get(1)
115+
if l := gc.(*LFUCache).freqList.Len(); l != 5 {
116+
t.Fatalf("%v != 5", l)
117+
}
118+
gc.Get(1)
119+
if l := gc.(*LFUCache).freqList.Len(); l != 6 {
120+
t.Fatalf("%v != 6", l)
121+
}
122+
}
123+
124+
func TestLFUFreqListLength(t *testing.T) {
125+
k0, v0 := "k0", "v0"
126+
k1, v1 := "k1", "v1"
127+
128+
{
129+
gc := buildTestCache(t, TYPE_LFU, 5)
130+
if l := gc.(*LFUCache).freqList.Len(); l != 1 {
131+
t.Fatalf("%v != 1", l)
132+
}
133+
}
134+
{
135+
gc := buildTestCache(t, TYPE_LFU, 5)
136+
gc.Set(k0, v0)
137+
for i := 0; i < 5; i++ {
138+
gc.Get(k0)
139+
}
140+
if l := gc.(*LFUCache).freqList.Len(); l != 2 {
141+
t.Fatalf("%v != 2", l)
142+
}
143+
}
144+
145+
{
146+
gc := buildTestCache(t, TYPE_LFU, 5)
147+
gc.Set(k0, v0)
148+
gc.Set(k1, v1)
149+
for i := 0; i < 5; i++ {
150+
gc.Get(k0)
151+
gc.Get(k1)
152+
}
153+
if l := gc.(*LFUCache).freqList.Len(); l != 2 {
154+
t.Fatalf("%v != 2", l)
155+
}
156+
}
157+
158+
{
159+
gc := buildTestCache(t, TYPE_LFU, 5)
160+
gc.Set(k0, v0)
161+
gc.Set(k1, v1)
162+
for i := 0; i < 5; i++ {
163+
gc.Get(k0)
164+
}
165+
if l := gc.(*LFUCache).freqList.Len(); l != 2 {
166+
t.Fatalf("%v != 2", l)
167+
}
168+
for i := 0; i < 5; i++ {
169+
gc.Get(k1)
170+
}
171+
if l := gc.(*LFUCache).freqList.Len(); l != 2 {
172+
t.Fatalf("%v != 2", l)
173+
}
174+
}
175+
176+
{
177+
gc := buildTestCache(t, TYPE_LFU, 5)
178+
gc.Set(k0, v0)
179+
gc.Get(k0)
180+
if l := gc.(*LFUCache).freqList.Len(); l != 2 {
181+
t.Fatalf("%v != 2", l)
182+
}
183+
gc.Remove(k0)
184+
if l := gc.(*LFUCache).freqList.Len(); l != 1 {
185+
t.Fatalf("%v != 1", l)
186+
}
187+
gc.Set(k0, v0)
188+
if l := gc.(*LFUCache).freqList.Len(); l != 1 {
189+
t.Fatalf("%v != 1", l)
190+
}
191+
gc.Get(k0)
192+
if l := gc.(*LFUCache).freqList.Len(); l != 2 {
193+
t.Fatalf("%v != 2", l)
194+
}
195+
}
196+
}

0 commit comments

Comments
 (0)
Please sign in to comment.