Skip to content

Commit 6e7b7b9

Browse files
committed
refactor(s3fifo): replace ringbuf queue with linked list queue in container/list package
1 parent 28caa2e commit 6e7b7b9

File tree

4 files changed

+56
-160
lines changed

4 files changed

+56
-160
lines changed

s3fifo/ringbuf.go

-68
This file was deleted.

s3fifo/ringbuf_test.go

-50
This file was deleted.

s3fifo/s3fifo.go

+55-41
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
package s3fifo
22

33
import (
4-
"fmt"
4+
"container/list"
55
"sync"
66

77
"github.com/scalalang2/golang-fifo"
88
)
99

10-
type entry[V any] struct {
10+
type entry[K comparable, V any] struct {
11+
key K
1112
value V
1213
freq byte
1314
}
@@ -19,18 +20,18 @@ type S3FIFO[K comparable, V any] struct {
1920
size int
2021

2122
// followings are the fundamental data structures of S3FIFO algorithm.
22-
items map[K]*entry[V]
23-
small *ringBuf[K]
24-
main *ringBuf[K]
23+
items map[K]*list.Element
24+
small *list.List
25+
main *list.List
2526
ghost *bucketTable[K]
2627
}
2728

2829
func New[K comparable, V any](size int) fifo.Cache[K, V] {
2930
return &S3FIFO[K, V]{
3031
size: size,
31-
items: make(map[K]*entry[V]),
32-
small: newRingBuf[K](size),
33-
main: newRingBuf[K](size),
32+
items: make(map[K]*list.Element),
33+
small: list.New(),
34+
main: list.New(),
3435
ghost: newBucketTable[K](size),
3536
}
3637
}
@@ -40,28 +41,29 @@ func (s *S3FIFO[K, V]) Set(key K, value V) {
4041
defer s.lock.Unlock()
4142

4243
if _, ok := s.items[key]; ok {
43-
s.items[key].value = value
44-
s.items[key].freq = min(s.items[key].freq+1, 3)
44+
el := s.items[key].Value.(*entry[K, V])
45+
el.value = value
46+
el.freq = min(el.freq+1, 3)
4547
return
4648
}
4749

48-
for s.small.length()+s.main.length() >= s.size {
50+
for s.small.Len()+s.main.Len() >= s.size {
4951
s.evict()
5052
}
5153

54+
// create a new entry to append it to the cache.
55+
ent := &entry[K, V]{
56+
key: key,
57+
value: value,
58+
freq: 0,
59+
}
60+
5261
if s.ghost.contains(key) {
5362
s.ghost.remove(key)
54-
if ok := s.main.push(key); !ok {
55-
panic("main ring buffer is full, this is unexpected bug")
56-
}
63+
s.items[key] = s.main.PushFront(ent)
5764
} else {
58-
if ok := s.small.push(key); !ok {
59-
panic(fmt.Errorf("small ring buffer is full, this is unexpected bug, len:%d, cap: %d", s.small.length(), s.small.capacity()))
60-
}
65+
s.items[key] = s.small.PushFront(ent)
6166
}
62-
63-
ent := &entry[V]{value: value, freq: 0}
64-
s.items[key] = ent
6567
}
6668

6769
func (s *S3FIFO[K, V]) Get(key K) (value V, ok bool) {
@@ -72,9 +74,10 @@ func (s *S3FIFO[K, V]) Get(key K) (value V, ok bool) {
7274
return value, false
7375
}
7476

75-
s.items[key].freq = min(s.items[key].freq+1, 3)
77+
ent := s.items[key].Value.(*entry[K, V])
78+
ent.freq = min(ent.freq+1, 3)
7679
s.ghost.remove(key)
77-
return s.items[key].value, true
80+
return ent.value, true
7881
}
7982

8083
func (s *S3FIFO[K, V]) Contains(key K) (ok bool) {
@@ -95,42 +98,50 @@ func (s *S3FIFO[K, V]) Peek(key K) (value V, ok bool) {
9598
if !ok {
9699
return value, false
97100
}
98-
return ent.value, ok
101+
return ent.Value.(*entry[K, V]).value, ok
99102
}
100103

101104
func (s *S3FIFO[K, V]) Len() int {
102-
return s.small.length() + s.main.length()
105+
return s.small.Len() + s.main.Len()
103106
}
104107

105108
func (s *S3FIFO[K, V]) Purge() {
106109
s.lock.Lock()
107110
defer s.lock.Unlock()
108111

109-
s.items = make(map[K]*entry[V])
110-
s.small = newRingBuf[K](s.size)
111-
s.main = newRingBuf[K](s.size)
112+
s.items = make(map[K]*list.Element)
113+
s.small = list.New()
114+
s.main = list.New()
112115
s.ghost = newBucketTable[K](s.size)
113116
}
114117

115118
func (s *S3FIFO[K, V]) evict() {
116-
mainCacheSize := s.size / 10 * 9
117-
if s.main.length() > mainCacheSize || s.small.length() == 0 {
118-
s.evictFromMain()
119+
// if size of the small queue is greater than 10% of the total cache size.
120+
// then, evict from the small queue
121+
if s.small.Len() > s.size/10 {
122+
s.evictFromSmall()
119123
return
120124
}
121-
s.evictFromSmall()
125+
s.evictFromMain()
122126
}
123127

124128
func (s *S3FIFO[K, V]) evictFromSmall() {
129+
mainCacheSize := s.size / 10 * 9
130+
125131
evicted := false
126-
for !evicted && !s.small.isEmpty() {
127-
key := s.small.pop()
128-
if s.items[key].freq > 1 {
129-
s.main.push(key)
130-
if s.main.isFull() {
132+
for !evicted && s.small.Len() > 0 {
133+
el := s.small.Back()
134+
key := el.Value.(*entry[K, V]).key
135+
if el.Value.(*entry[K, V]).freq > 1 {
136+
// move the entry from the small queue to the main queue
137+
s.small.Remove(el)
138+
s.items[key] = s.main.PushFront(el.Value)
139+
140+
if s.main.Len() > mainCacheSize {
131141
s.evictFromMain()
132142
}
133143
} else {
144+
s.small.Remove(el)
134145
s.ghost.add(key)
135146
evicted = true
136147
delete(s.items, key)
@@ -140,12 +151,15 @@ func (s *S3FIFO[K, V]) evictFromSmall() {
140151

141152
func (s *S3FIFO[K, V]) evictFromMain() {
142153
evicted := false
143-
for !evicted && !s.main.isEmpty() {
144-
key := s.main.pop()
145-
if s.items[key].freq > 0 {
146-
s.main.push(key)
147-
s.items[key].freq--
154+
for !evicted && s.main.Len() > 0 {
155+
el := s.main.Back()
156+
key := el.Value.(*entry[K, V]).key
157+
if el.Value.(*entry[K, V]).freq > 0 {
158+
s.main.Remove(el)
159+
s.items[key] = s.main.PushFront(el.Value)
160+
el.Value.(*entry[K, V]).freq -= 1
148161
} else {
162+
s.main.Remove(el)
149163
evicted = true
150164
delete(s.items, key)
151165
}

s3fifo/s3fifo_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ func TestCleanOnCache(t *testing.T) {
133133
cache.Set(v, v*10)
134134
}
135135
require.Equal(t, 5, cache.Len())
136-
cache.Clean()
136+
cache.Purge()
137137

138138
// check if each entry exists in the cache
139139
for _, v := range entries {

0 commit comments

Comments
 (0)