1
1
package s3fifo
2
2
3
3
import (
4
- "fmt "
4
+ "container/list "
5
5
"sync"
6
6
7
7
"github.com/scalalang2/golang-fifo"
8
8
)
9
9
10
- type entry [V any ] struct {
10
+ type entry [K comparable , V any ] struct {
11
+ key K
11
12
value V
12
13
freq byte
13
14
}
@@ -19,18 +20,18 @@ type S3FIFO[K comparable, V any] struct {
19
20
size int
20
21
21
22
// 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
25
26
ghost * bucketTable [K ]
26
27
}
27
28
28
29
func New [K comparable , V any ](size int ) fifo.Cache [K , V ] {
29
30
return & S3FIFO [K , V ]{
30
31
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 ( ),
34
35
ghost : newBucketTable [K ](size ),
35
36
}
36
37
}
@@ -40,28 +41,29 @@ func (s *S3FIFO[K, V]) Set(key K, value V) {
40
41
defer s .lock .Unlock ()
41
42
42
43
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 )
45
47
return
46
48
}
47
49
48
- for s .small .length ()+ s .main .length () >= s .size {
50
+ for s .small .Len ()+ s .main .Len () >= s .size {
49
51
s .evict ()
50
52
}
51
53
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
+
52
61
if s .ghost .contains (key ) {
53
62
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 )
57
64
} 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 )
61
66
}
62
-
63
- ent := & entry [V ]{value : value , freq : 0 }
64
- s .items [key ] = ent
65
67
}
66
68
67
69
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) {
72
74
return value , false
73
75
}
74
76
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 )
76
79
s .ghost .remove (key )
77
- return s . items [ key ] .value , true
80
+ return ent .value , true
78
81
}
79
82
80
83
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) {
95
98
if ! ok {
96
99
return value , false
97
100
}
98
- return ent .value , ok
101
+ return ent .Value .( * entry [ K , V ]). value , ok
99
102
}
100
103
101
104
func (s * S3FIFO [K , V ]) Len () int {
102
- return s .small .length () + s .main .length ()
105
+ return s .small .Len () + s .main .Len ()
103
106
}
104
107
105
108
func (s * S3FIFO [K , V ]) Purge () {
106
109
s .lock .Lock ()
107
110
defer s .lock .Unlock ()
108
111
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 ( )
112
115
s .ghost = newBucketTable [K ](s .size )
113
116
}
114
117
115
118
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 ()
119
123
return
120
124
}
121
- s .evictFromSmall ()
125
+ s .evictFromMain ()
122
126
}
123
127
124
128
func (s * S3FIFO [K , V ]) evictFromSmall () {
129
+ mainCacheSize := s .size / 10 * 9
130
+
125
131
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 {
131
141
s .evictFromMain ()
132
142
}
133
143
} else {
144
+ s .small .Remove (el )
134
145
s .ghost .add (key )
135
146
evicted = true
136
147
delete (s .items , key )
@@ -140,12 +151,15 @@ func (s *S3FIFO[K, V]) evictFromSmall() {
140
151
141
152
func (s * S3FIFO [K , V ]) evictFromMain () {
142
153
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
148
161
} else {
162
+ s .main .Remove (el )
149
163
evicted = true
150
164
delete (s .items , key )
151
165
}
0 commit comments