blob: 555225a218c968e756812976829e3d548e7bffda [file] [log] [blame]
Matthias Andreas Benkard832a54e2019-01-29 09:27:38 +01001package lru
2
3import (
4 "sync"
5
6 "github.com/hashicorp/golang-lru/simplelru"
7)
8
9// ARCCache is a thread-safe fixed size Adaptive Replacement Cache (ARC).
10// ARC is an enhancement over the standard LRU cache in that tracks both
11// frequency and recency of use. This avoids a burst in access to new
12// entries from evicting the frequently used older entries. It adds some
13// additional tracking overhead to a standard LRU cache, computationally
14// it is roughly 2x the cost, and the extra memory overhead is linear
15// with the size of the cache. ARC has been patented by IBM, but is
16// similar to the TwoQueueCache (2Q) which requires setting parameters.
17type ARCCache struct {
18 size int // Size is the total capacity of the cache
19 p int // P is the dynamic preference towards T1 or T2
20
21 t1 simplelru.LRUCache // T1 is the LRU for recently accessed items
22 b1 simplelru.LRUCache // B1 is the LRU for evictions from t1
23
24 t2 simplelru.LRUCache // T2 is the LRU for frequently accessed items
25 b2 simplelru.LRUCache // B2 is the LRU for evictions from t2
26
27 lock sync.RWMutex
28}
29
30// NewARC creates an ARC of the given size
31func NewARC(size int) (*ARCCache, error) {
32 // Create the sub LRUs
33 b1, err := simplelru.NewLRU(size, nil)
34 if err != nil {
35 return nil, err
36 }
37 b2, err := simplelru.NewLRU(size, nil)
38 if err != nil {
39 return nil, err
40 }
41 t1, err := simplelru.NewLRU(size, nil)
42 if err != nil {
43 return nil, err
44 }
45 t2, err := simplelru.NewLRU(size, nil)
46 if err != nil {
47 return nil, err
48 }
49
50 // Initialize the ARC
51 c := &ARCCache{
52 size: size,
53 p: 0,
54 t1: t1,
55 b1: b1,
56 t2: t2,
57 b2: b2,
58 }
59 return c, nil
60}
61
62// Get looks up a key's value from the cache.
63func (c *ARCCache) Get(key interface{}) (value interface{}, ok bool) {
64 c.lock.Lock()
65 defer c.lock.Unlock()
66
67 // If the value is contained in T1 (recent), then
68 // promote it to T2 (frequent)
69 if val, ok := c.t1.Peek(key); ok {
70 c.t1.Remove(key)
71 c.t2.Add(key, val)
72 return val, ok
73 }
74
75 // Check if the value is contained in T2 (frequent)
76 if val, ok := c.t2.Get(key); ok {
77 return val, ok
78 }
79
80 // No hit
81 return nil, false
82}
83
84// Add adds a value to the cache.
85func (c *ARCCache) Add(key, value interface{}) {
86 c.lock.Lock()
87 defer c.lock.Unlock()
88
89 // Check if the value is contained in T1 (recent), and potentially
90 // promote it to frequent T2
91 if c.t1.Contains(key) {
92 c.t1.Remove(key)
93 c.t2.Add(key, value)
94 return
95 }
96
97 // Check if the value is already in T2 (frequent) and update it
98 if c.t2.Contains(key) {
99 c.t2.Add(key, value)
100 return
101 }
102
103 // Check if this value was recently evicted as part of the
104 // recently used list
105 if c.b1.Contains(key) {
106 // T1 set is too small, increase P appropriately
107 delta := 1
108 b1Len := c.b1.Len()
109 b2Len := c.b2.Len()
110 if b2Len > b1Len {
111 delta = b2Len / b1Len
112 }
113 if c.p+delta >= c.size {
114 c.p = c.size
115 } else {
116 c.p += delta
117 }
118
119 // Potentially need to make room in the cache
120 if c.t1.Len()+c.t2.Len() >= c.size {
121 c.replace(false)
122 }
123
124 // Remove from B1
125 c.b1.Remove(key)
126
127 // Add the key to the frequently used list
128 c.t2.Add(key, value)
129 return
130 }
131
132 // Check if this value was recently evicted as part of the
133 // frequently used list
134 if c.b2.Contains(key) {
135 // T2 set is too small, decrease P appropriately
136 delta := 1
137 b1Len := c.b1.Len()
138 b2Len := c.b2.Len()
139 if b1Len > b2Len {
140 delta = b1Len / b2Len
141 }
142 if delta >= c.p {
143 c.p = 0
144 } else {
145 c.p -= delta
146 }
147
148 // Potentially need to make room in the cache
149 if c.t1.Len()+c.t2.Len() >= c.size {
150 c.replace(true)
151 }
152
153 // Remove from B2
154 c.b2.Remove(key)
155
156 // Add the key to the frequently used list
157 c.t2.Add(key, value)
158 return
159 }
160
161 // Potentially need to make room in the cache
162 if c.t1.Len()+c.t2.Len() >= c.size {
163 c.replace(false)
164 }
165
166 // Keep the size of the ghost buffers trim
167 if c.b1.Len() > c.size-c.p {
168 c.b1.RemoveOldest()
169 }
170 if c.b2.Len() > c.p {
171 c.b2.RemoveOldest()
172 }
173
174 // Add to the recently seen list
175 c.t1.Add(key, value)
176 return
177}
178
179// replace is used to adaptively evict from either T1 or T2
180// based on the current learned value of P
181func (c *ARCCache) replace(b2ContainsKey bool) {
182 t1Len := c.t1.Len()
183 if t1Len > 0 && (t1Len > c.p || (t1Len == c.p && b2ContainsKey)) {
184 k, _, ok := c.t1.RemoveOldest()
185 if ok {
186 c.b1.Add(k, nil)
187 }
188 } else {
189 k, _, ok := c.t2.RemoveOldest()
190 if ok {
191 c.b2.Add(k, nil)
192 }
193 }
194}
195
196// Len returns the number of cached entries
197func (c *ARCCache) Len() int {
198 c.lock.RLock()
199 defer c.lock.RUnlock()
200 return c.t1.Len() + c.t2.Len()
201}
202
203// Keys returns all the cached keys
204func (c *ARCCache) Keys() []interface{} {
205 c.lock.RLock()
206 defer c.lock.RUnlock()
207 k1 := c.t1.Keys()
208 k2 := c.t2.Keys()
209 return append(k1, k2...)
210}
211
212// Remove is used to purge a key from the cache
213func (c *ARCCache) Remove(key interface{}) {
214 c.lock.Lock()
215 defer c.lock.Unlock()
216 if c.t1.Remove(key) {
217 return
218 }
219 if c.t2.Remove(key) {
220 return
221 }
222 if c.b1.Remove(key) {
223 return
224 }
225 if c.b2.Remove(key) {
226 return
227 }
228}
229
230// Purge is used to clear the cache
231func (c *ARCCache) Purge() {
232 c.lock.Lock()
233 defer c.lock.Unlock()
234 c.t1.Purge()
235 c.t2.Purge()
236 c.b1.Purge()
237 c.b2.Purge()
238}
239
240// Contains is used to check if the cache contains a key
241// without updating recency or frequency.
242func (c *ARCCache) Contains(key interface{}) bool {
243 c.lock.RLock()
244 defer c.lock.RUnlock()
245 return c.t1.Contains(key) || c.t2.Contains(key)
246}
247
248// Peek is used to inspect the cache value of a key
249// without updating recency or frequency.
250func (c *ARCCache) Peek(key interface{}) (value interface{}, ok bool) {
251 c.lock.RLock()
252 defer c.lock.RUnlock()
253 if val, ok := c.t1.Peek(key); ok {
254 return val, ok
255 }
256 return c.t2.Peek(key)
257}