vitess.io/vitess@v0.16.2/go/cache/lru_cache.go (about) 1 /* 2 Copyright 2019 The Vitess Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 // Package cache implements a LRU cache. 18 // 19 // The implementation borrows heavily from SmallLRUCache 20 // (originally by Nathan Schrenk). The object maintains a doubly-linked list of 21 // elements. When an element is accessed, it is promoted to the head of the 22 // list. When space is needed, the element at the tail of the list 23 // (the least recently used element) is evicted. 24 package cache 25 26 import ( 27 "container/list" 28 "sync" 29 "time" 30 ) 31 32 var _ Cache = &LRUCache{} 33 34 // LRUCache is a typical LRU cache implementation. If the cache 35 // reaches the capacity, the least recently used item is deleted from 36 // the cache. Note the capacity is not the number of items, but the 37 // total sum of the CachedSize() of each item. 38 type LRUCache struct { 39 mu sync.Mutex 40 41 // list & table contain *entry objects. 42 list *list.List 43 table map[string]*list.Element 44 cost func(any) int64 45 46 size int64 47 capacity int64 48 evictions int64 49 hits int64 50 misses int64 51 } 52 53 // Item is what is stored in the cache 54 type Item struct { 55 Key string 56 Value any 57 } 58 59 type entry struct { 60 key string 61 value any 62 size int64 63 timeAccessed time.Time 64 } 65 66 // NewLRUCache creates a new empty cache with the given capacity. 67 func NewLRUCache(capacity int64, cost func(any) int64) *LRUCache { 68 return &LRUCache{ 69 list: list.New(), 70 table: make(map[string]*list.Element), 71 capacity: capacity, 72 cost: cost, 73 } 74 } 75 76 // Get returns a value from the cache, and marks the entry as most 77 // recently used. 78 func (lru *LRUCache) Get(key string) (v any, ok bool) { 79 lru.mu.Lock() 80 defer lru.mu.Unlock() 81 82 element := lru.table[key] 83 if element == nil { 84 lru.misses++ 85 return nil, false 86 } 87 lru.moveToFront(element) 88 lru.hits++ 89 return element.Value.(*entry).value, true 90 } 91 92 // Set sets a value in the cache. 93 func (lru *LRUCache) Set(key string, value any) bool { 94 lru.mu.Lock() 95 defer lru.mu.Unlock() 96 97 if element := lru.table[key]; element != nil { 98 lru.updateInplace(element, value) 99 } else { 100 lru.addNew(key, value) 101 } 102 // the LRU cache cannot fail to insert items; it always returns true 103 return true 104 } 105 106 // Delete removes an entry from the cache, and returns if the entry existed. 107 func (lru *LRUCache) delete(key string) bool { 108 lru.mu.Lock() 109 defer lru.mu.Unlock() 110 111 element := lru.table[key] 112 if element == nil { 113 return false 114 } 115 116 lru.list.Remove(element) 117 delete(lru.table, key) 118 lru.size -= element.Value.(*entry).size 119 return true 120 } 121 122 // Delete removes an entry from the cache 123 func (lru *LRUCache) Delete(key string) { 124 lru.delete(key) 125 } 126 127 // Clear will clear the entire cache. 128 func (lru *LRUCache) Clear() { 129 lru.mu.Lock() 130 defer lru.mu.Unlock() 131 132 lru.list.Init() 133 lru.table = make(map[string]*list.Element) 134 lru.size = 0 135 } 136 137 // Len returns the size of the cache (in entries) 138 func (lru *LRUCache) Len() int { 139 lru.mu.Lock() 140 defer lru.mu.Unlock() 141 return lru.list.Len() 142 } 143 144 // SetCapacity will set the capacity of the cache. If the capacity is 145 // smaller, and the current cache size exceed that capacity, the cache 146 // will be shrank. 147 func (lru *LRUCache) SetCapacity(capacity int64) { 148 lru.mu.Lock() 149 defer lru.mu.Unlock() 150 151 lru.capacity = capacity 152 lru.checkCapacity() 153 } 154 155 // Wait is a no-op in the LRU cache 156 func (lru *LRUCache) Wait() {} 157 158 // UsedCapacity returns the size of the cache (in bytes) 159 func (lru *LRUCache) UsedCapacity() int64 { 160 return lru.size 161 } 162 163 // MaxCapacity returns the cache maximum capacity. 164 func (lru *LRUCache) MaxCapacity() int64 { 165 lru.mu.Lock() 166 defer lru.mu.Unlock() 167 return lru.capacity 168 } 169 170 // Evictions returns the number of evictions 171 func (lru *LRUCache) Evictions() int64 { 172 lru.mu.Lock() 173 defer lru.mu.Unlock() 174 return lru.evictions 175 } 176 177 // Hits returns number of cache hits since creation 178 func (lru *LRUCache) Hits() int64 { 179 lru.mu.Lock() 180 defer lru.mu.Unlock() 181 return lru.hits 182 } 183 184 // Misses returns number of cache misses since creation 185 func (lru *LRUCache) Misses() int64 { 186 lru.mu.Lock() 187 defer lru.mu.Unlock() 188 return lru.misses 189 } 190 191 // ForEach yields all the values for the cache, ordered from most recently 192 // used to least recently used. 193 func (lru *LRUCache) ForEach(callback func(value any) bool) { 194 lru.mu.Lock() 195 defer lru.mu.Unlock() 196 197 for e := lru.list.Front(); e != nil; e = e.Next() { 198 v := e.Value.(*entry) 199 if !callback(v.value) { 200 break 201 } 202 } 203 } 204 205 // Items returns all the values for the cache, ordered from most recently 206 // used to least recently used. 207 func (lru *LRUCache) Items() []Item { 208 lru.mu.Lock() 209 defer lru.mu.Unlock() 210 211 items := make([]Item, 0, lru.list.Len()) 212 for e := lru.list.Front(); e != nil; e = e.Next() { 213 v := e.Value.(*entry) 214 items = append(items, Item{Key: v.key, Value: v.value}) 215 } 216 return items 217 } 218 219 func (lru *LRUCache) updateInplace(element *list.Element, value any) { 220 valueSize := lru.cost(value) 221 sizeDiff := valueSize - element.Value.(*entry).size 222 element.Value.(*entry).value = value 223 element.Value.(*entry).size = valueSize 224 lru.size += sizeDiff 225 lru.moveToFront(element) 226 lru.checkCapacity() 227 } 228 229 func (lru *LRUCache) moveToFront(element *list.Element) { 230 lru.list.MoveToFront(element) 231 element.Value.(*entry).timeAccessed = time.Now() 232 } 233 234 func (lru *LRUCache) addNew(key string, value any) { 235 newEntry := &entry{key, value, lru.cost(value), time.Now()} 236 element := lru.list.PushFront(newEntry) 237 lru.table[key] = element 238 lru.size += newEntry.size 239 lru.checkCapacity() 240 } 241 242 func (lru *LRUCache) checkCapacity() { 243 // Partially duplicated from Delete 244 for lru.size > lru.capacity { 245 delElem := lru.list.Back() 246 delValue := delElem.Value.(*entry) 247 lru.list.Remove(delElem) 248 delete(lru.table, delValue.key) 249 lru.size -= delValue.size 250 lru.evictions++ 251 } 252 }