-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy paths3fifo.go
155 lines (139 loc) · 3.12 KB
/
s3fifo.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
package fifo
import (
"bytes"
"fmt"
"log"
)
type s3item[K comparable, V any] struct {
key K
value V
freq int
}
// S3FIFO implements S3-FIFO.
// See also https://s3fifo.com/
type S3FIFO[K comparable, V any] struct {
small FIFO[s3item[K, V]]
main FIFO[s3item[K, V]]
ghost FIFO[K]
smallSize int
mainSize int
}
func NewS3FIFO[K comparable, V any](smallSize, mainSize int) *S3FIFO[K, V] {
if smallSize < 1 {
smallSize = 1
}
if mainSize < smallSize {
mainSize = smallSize
}
return &S3FIFO[K, V]{
smallSize: smallSize,
mainSize: mainSize,
}
}
func (cache *S3FIFO[K, V]) Size() int {
return cache.smallSize + cache.mainSize
}
func (cache *S3FIFO[K, V]) Len() int {
return cache.small.Len() + cache.main.Len()
}
func (cache *S3FIFO[K, V]) inSmallOrMain(k K) (*s3item[K, V], bool) {
equalKey := func(item s3item[K, V]) bool {
return item.key == k
}
if p, ok := cache.small.Find(equalKey); ok {
return p, true
}
return cache.main.Find(equalKey)
}
func (cache *S3FIFO[K, V]) Get(k K) (V, bool) {
item, ok := cache.inSmallOrMain(k)
if !ok {
var zero V
return zero, false
}
if item.freq < 3 {
item.freq++
}
return item.value, true
}
func (cache *S3FIFO[K, V]) Put(k K, v V) {
item, ok := cache.inSmallOrMain(k)
if ok {
// update a cached item.
item.value = v
if item.freq < 3 {
item.freq++
}
return
}
cache.insertNew(s3item[K, V]{key: k, value: v, freq: 0})
}
func (cache *S3FIFO[K, V]) insertNew(item s3item[K, V]) {
if cache.removeFromGhost(item.key) {
log.Printf("removed from ghost, move to main: key=%+v", item.key)
cache.evictM()
cache.main.Insert(item)
} else {
cache.evictS()
cache.small.Insert(item)
}
}
func (cache *S3FIFO[K, V]) removeFromGhost(k K) bool {
return cache.ghost.RemoveIf(func(v K) bool {
return v == k
})
}
func (cache *S3FIFO[K, V]) evictM() {
for cache.main.Len() >= cache.mainSize {
item, _ := cache.main.Evict()
if item.freq > 0 {
item.freq--
cache.main.Insert(item)
} else {
log.Printf("evicted from main: key=%+v value=%+v", item.key, item.value)
}
}
}
func (cache *S3FIFO[K, V]) evictS() {
for cache.small.Len() >= cache.smallSize {
item, _ := cache.small.Evict()
if item.freq > 0 {
cache.evictM()
item.freq = 0
cache.main.Insert(item)
log.Printf("evicted from small, move to main: item=%+v", item)
} else {
for cache.ghost.Len() >= cache.mainSize {
cache.ghost.Evict()
}
cache.ghost.Insert(item.key)
log.Printf("evicted from small, move to ghost, value dropped: item=%+v", item)
}
}
}
func enumerateFIFO[V any](e *entry[V], fn func(v V)) {
if e == nil {
return
}
enumerateFIFO(e.prev, fn)
fn(e.value)
}
func (cache *S3FIFO[K, V]) Dump() {
bb := &bytes.Buffer{}
fmt.Fprintf(bb, "s[")
enumerateFIFO(cache.small.tail, func(item s3item[K, V]) {
fmt.Fprintf(bb, " %+v", item)
})
fmt.Fprintf(bb, "]\n")
fmt.Fprintf(bb, "\t\t m[")
enumerateFIFO(cache.main.tail, func(item s3item[K, V]) {
fmt.Fprintf(bb, " %+v", item)
})
fmt.Fprintf(bb, "]\n")
fmt.Fprintf(bb, "\t\t g[")
enumerateFIFO(cache.ghost.tail, func(k K) {
fmt.Fprintf(bb, " %+v", k)
})
fmt.Fprintf(bb, "]\n")
log.Print(bb.String())
}