github.com/blend/go-sdk@v1.20220411.3/cache/lru_queue.go (about) 1 /* 2 3 Copyright (c) 2022 - Present. Blend Labs, Inc. All rights reserved 4 Use of this source code is governed by a MIT license that can be found in the LICENSE file. 5 6 */ 7 8 package cache 9 10 import ( 11 "sort" 12 ) 13 14 var ( 15 _ LRU = (*LRUQueue)(nil) 16 ) 17 18 // NewLRUQueue creates a new, empty, LRUQueue. 19 func NewLRUQueue() *LRUQueue { 20 return &LRUQueue{ 21 array: make([]*Value, ringBufferDefaultCapacity), 22 } 23 } 24 25 // LRUQueue is a fifo buffer that is backed by a pre-allocated array, instead of allocating 26 // a whole new node object for each element (which saves GC churn). 27 // Enqueue can be O(n), Dequeue can be O(1). 28 type LRUQueue struct { 29 array []*Value 30 head int 31 tail int 32 size int 33 } 34 35 // Len returns the length of the ring buffer (as it is currently populated). 36 // Actual memory footprint may be different. 37 func (lru *LRUQueue) Len() (len int) { 38 return lru.size 39 } 40 41 // Capacity returns the total size of the ring bufffer, including empty elements. 42 func (lru *LRUQueue) Capacity() int { 43 return len(lru.array) 44 } 45 46 // Clear removes all objects from the LRUQueue. 47 func (lru *LRUQueue) Clear() { 48 if lru.head < lru.tail { 49 arrayClear(lru.array, lru.head, lru.size) 50 } else { 51 arrayClear(lru.array, lru.head, len(lru.array)-lru.head) 52 arrayClear(lru.array, 0, lru.tail) 53 } 54 lru.head = 0 55 lru.tail = 0 56 lru.size = 0 57 } 58 59 // Push adds an element to the "back" of the LRUQueue. 60 func (lru *LRUQueue) Push(object *Value) { 61 if lru.size == len(lru.array) { // if we're out of room 62 lru.setCapacity(lru.growCapacity()) 63 } 64 lru.array[lru.tail] = object 65 lru.tail = (lru.tail + 1) % len(lru.array) 66 lru.size++ 67 } 68 69 // Pop removes the first (oldest) element from the LRUQueue. 70 func (lru *LRUQueue) Pop() *Value { 71 if lru.size == 0 { 72 return nil 73 } 74 75 removed := lru.array[lru.head] 76 lru.head = (lru.head + 1) % len(lru.array) 77 lru.size-- 78 return removed 79 } 80 81 // Peek returns but does not remove the first element. 82 func (lru *LRUQueue) Peek() *Value { 83 if lru.size == 0 { 84 return nil 85 } 86 return lru.array[lru.head] 87 } 88 89 // PeekBack returns but does not remove the last element. 90 func (lru *LRUQueue) PeekBack() *Value { 91 if lru.size == 0 { 92 return nil 93 } 94 if lru.tail == 0 { 95 return lru.array[len(lru.array)-1] 96 } 97 return lru.array[lru.tail-1] 98 } 99 100 // Fix updates the queue given an update to a specific value. 101 func (lru *LRUQueue) Fix(value *Value) { 102 if lru.size == 0 { 103 return 104 } 105 if value == nil { 106 panic("lru queue; value is nil") 107 } 108 109 values := make([]*Value, lru.size) 110 var index int 111 var didUpdate bool 112 lru.Each(func(v *Value) bool { 113 if v.Key == value.Key { 114 didUpdate = v.Expires != value.Expires 115 values[index] = value 116 } else { 117 values[index] = v 118 } 119 index++ 120 return true 121 }) 122 if didUpdate { 123 sort.Sort(LRUHeapValues(values)) 124 } 125 lru.array = make([]*Value, len(lru.array)) 126 copy(lru.array, values) 127 lru.head = 0 128 lru.tail = lru.size 129 } 130 131 // Remove removes an item from the queue by its key. 132 func (lru *LRUQueue) Remove(key interface{}) { 133 if lru.size == 0 { 134 return 135 } 136 if key == nil { 137 panic("lru queue; key is nil") 138 } 139 140 size := lru.size 141 142 values := make([]*Value, size-1) 143 var cursor int 144 for x := 0; x < size; x++ { 145 head := lru.Pop() 146 if head.Key != key { 147 values[cursor] = head 148 cursor++ 149 } 150 } 151 for x := 0; x < len(values); x++ { 152 lru.Push(values[x]) 153 } 154 } 155 156 // Each iterates through the queue and calls the consumer for each element of the queue. 157 func (lru *LRUQueue) Each(consumer func(*Value) bool) { 158 if lru.size == 0 { 159 return 160 } 161 162 if lru.head < lru.tail { 163 for cursor := lru.head; cursor < lru.tail; cursor++ { 164 if !consumer(lru.array[cursor]) { 165 return 166 } 167 } 168 } else { 169 for cursor := lru.head; cursor < len(lru.array); cursor++ { 170 if !consumer(lru.array[cursor]) { 171 return 172 } 173 } 174 for cursor := 0; cursor < lru.tail; cursor++ { 175 if !consumer(lru.array[cursor]) { 176 return 177 } 178 } 179 } 180 } 181 182 // Consume calls the consumer for each element in the buffer. If the handler returns true, 183 // the element is popped and the handler is called on the next value. 184 func (lru *LRUQueue) Consume(consumer func(*Value) bool) { 185 if lru.size == 0 { 186 return 187 } 188 189 for i := 0; i < lru.size; i++ { 190 if !consumer(lru.Peek()) { 191 return 192 } 193 lru.Pop() 194 } 195 } 196 197 // Reset removes all elements from the heap, leaving an empty heap. 198 func (lru *LRUQueue) Reset() { 199 lru.array = make([]*Value, ringBufferDefaultCapacity) 200 lru.head = 0 201 lru.tail = 0 202 lru.size = 0 203 } 204 205 // 206 // util / helpers 207 // 208 209 // TrimExcess trims the excess space in the ringbuffer. 210 func (lru *LRUQueue) TrimExcess() { 211 threshold := float64(len(lru.array)) * 0.9 212 if lru.size < int(threshold) { 213 lru.setCapacity(lru.size) 214 } 215 } 216 217 // 218 // internal helpers 219 // 220 221 func (lru *LRUQueue) growCapacity() int { 222 size := len(lru.array) 223 newCapacity := size << 1 224 minimumGrow := size + ringBufferMinimumGrow 225 226 if newCapacity < minimumGrow { 227 newCapacity = minimumGrow 228 } 229 230 return newCapacity 231 } 232 233 func (lru *LRUQueue) setCapacity(capacity int) { 234 newArray := make([]*Value, capacity) 235 if lru.size > 0 { 236 if lru.head < lru.tail { 237 arrayCopy(lru.array, lru.head, newArray, 0, lru.size) 238 } else { 239 arrayCopy(lru.array, lru.head, newArray, 0, len(lru.array)-lru.head) 240 arrayCopy(lru.array, 0, newArray, len(lru.array)-lru.head, lru.tail) 241 } 242 } 243 lru.array = newArray 244 lru.head = 0 245 if lru.size == capacity { 246 lru.tail = 0 247 } else { 248 lru.tail = lru.size 249 } 250 } 251 252 // 253 // array helpers 254 // 255 256 func arrayClear(source []*Value, index, length int) { 257 for x := 0; x < length; x++ { 258 absoluteIndex := x + index 259 source[absoluteIndex] = nil 260 } 261 } 262 263 func arrayCopy(source []*Value, sourceIndex int, destination []*Value, destinationIndex, length int) { 264 for x := 0; x < length; x++ { 265 from := sourceIndex + x 266 to := destinationIndex + x 267 268 destination[to] = source[from] 269 } 270 } 271 272 const ( 273 ringBufferMinimumGrow = 4 274 ringBufferDefaultCapacity = 4 275 )