-
Notifications
You must be signed in to change notification settings - Fork 2
/
lru.go
218 lines (196 loc) · 5.39 KB
/
lru.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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
package cache
import (
"container/list"
)
// lruCache is a LRU cache.
type lruCache struct {
cache *cache
cap int
ls list.List
}
// init initializes cache list.
func (l *lruCache) init(c *cache, cap int) {
l.cache = c
l.cap = cap
l.ls.Init()
}
// write adds new entry to the cache and returns evicted entry if necessary.
func (l *lruCache) write(en *entry) *entry {
// Fast path
if en.accessList != nil {
// Entry existed, update its status instead.
l.markAccess(en)
return nil
}
// Try to add new entry to the list
cen := l.cache.getOrSet(en)
if cen == nil {
// Brand new entry, add to the LRU list.
en.accessList = l.ls.PushFront(en)
} else {
// Entry has already been added, update its value instead.
cen.setValue(en.getValue())
cen.setWriteTime(en.getWriteTime())
if cen.accessList == nil {
// Entry is loaded to the cache but not yet registered.
cen.accessList = l.ls.PushFront(cen)
} else {
l.markAccess(cen)
}
}
if l.cap > 0 && l.ls.Len() > l.cap {
// Remove the last element when capacity exceeded.
en = getEntry(l.ls.Back())
return l.remove(en)
}
return nil
}
// access updates cache entry for a get.
func (l *lruCache) access(en *entry) {
if en.accessList != nil {
l.markAccess(en)
}
}
// markAccess marks the element has just been accessed.
// en.accessList must not be null.
func (l *lruCache) markAccess(en *entry) {
l.ls.MoveToFront(en.accessList)
}
// remove removes an entry from the cache.
func (l *lruCache) remove(en *entry) *entry {
if en.accessList == nil {
// Already deleted
return nil
}
l.cache.delete(en)
l.ls.Remove(en.accessList)
en.accessList = nil
return en
}
// iterate walks through all lists by access time.
func (l *lruCache) iterate(fn func(en *entry) bool) {
iterateListFromBack(&l.ls, fn)
}
const (
admissionWindow uint8 = iota
probationSegment
protectedSegment
)
const (
protectedRatio = 0.8
)
// slruCache is a segmented LRU.
// See http://highscalability.com/blog/2016/1/25/design-of-a-modern-cache.html
type slruCache struct {
cache *cache
probationCap int
probationLs list.List
protectedCap int
protectedLs list.List
}
// init initializes the cache list.
func (l *slruCache) init(c *cache, cap int) {
l.cache = c
l.protectedCap = int(float64(cap) * protectedRatio)
l.probationCap = cap - l.protectedCap
l.probationLs.Init()
l.protectedLs.Init()
}
// length returns total number of entries in the cache.
func (l *slruCache) length() int {
return l.probationLs.Len() + l.protectedLs.Len()
}
// write adds new entry to the cache and returns evicted entry if necessary.
func (l *slruCache) write(en *entry) *entry {
// Fast path
if en.accessList != nil {
// Entry existed, update its value instead.
l.markAccess(en)
return nil
}
// Try to add new entry to the probation segment.
cen := l.cache.getOrSet(en)
if cen == nil {
// Brand new entry, add to the probation segment.
en.listID = probationSegment
en.accessList = l.probationLs.PushFront(en)
} else {
// Entry has already been added, update its value instead.
cen.setValue(en.getValue())
cen.setWriteTime(en.getWriteTime())
if cen.accessList == nil {
// Entry is loaded to the cache but not yet registered.
cen.listID = probationSegment
cen.accessList = l.probationLs.PushFront(cen)
} else {
l.markAccess(cen)
}
}
// The probation list can exceed its capacity if number of entries
// is still under total allowed capacity.
if l.probationCap > 0 && l.probationLs.Len() > l.probationCap &&
l.length() > (l.probationCap+l.protectedCap) {
// Remove the last element when capacity exceeded.
en = getEntry(l.probationLs.Back())
return l.remove(en)
}
return nil
}
// access updates cache entry for a get.
func (l *slruCache) access(en *entry) {
if en.accessList != nil {
l.markAccess(en)
}
}
// markAccess marks the element has just been accessed.
// en.accessList must not be null.
func (l *slruCache) markAccess(en *entry) {
if en.listID == protectedSegment {
// Already in the protected segment.
l.protectedLs.MoveToFront(en.accessList)
return
}
// The entry is currently in the probation segment, promote it to the protected segment.
en.listID = protectedSegment
l.probationLs.Remove(en.accessList)
en.accessList = l.protectedLs.PushFront(en)
if l.protectedCap > 0 && l.protectedLs.Len() > l.protectedCap {
// Protected list capacity exceeded, move the last entry in the protected segment to
// the probation segment.
en = getEntry(l.protectedLs.Back())
en.listID = probationSegment
l.protectedLs.Remove(en.accessList)
en.accessList = l.probationLs.PushFront(en)
}
}
// remove removes an entry from the cache and returns the removed entry or nil
// if it is not found.
func (l *slruCache) remove(en *entry) *entry {
if en.accessList == nil {
return nil
}
l.cache.delete(en)
if en.listID == protectedSegment {
l.protectedLs.Remove(en.accessList)
} else {
l.probationLs.Remove(en.accessList)
}
en.accessList = nil
return en
}
// victim returns the last entry in probation list if total entries reached the limit.
func (l *slruCache) victim() *entry {
if l.probationCap <= 0 || l.length() < (l.probationCap+l.protectedCap) {
return nil
}
el := l.probationLs.Back()
if el == nil {
return nil
}
return getEntry(el)
}
// iterate walks through all lists by access time.
func (l *slruCache) iterate(fn func(en *entry) bool) {
iterateListFromBack(&l.protectedLs, fn)
iterateListFromBack(&l.probationLs, fn)
}