11package s3fifo
22
33import (
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
2829func 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
6769func (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
8083func (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
101104func (s * S3FIFO [K , V ]) Len () int {
102- return s .small .length () + s .main .length ()
105+ return s .small .Len () + s .main .Len ()
103106}
104107
105108func (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
115118func (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
124128func (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
141152func (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 }
0 commit comments