github.com/matrixorigin/matrixone@v1.2.0/pkg/fileservice/fifocache/fifo.go (about) 1 // Copyright 2024 Matrix Origin 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package fifocache 16 17 import ( 18 "sync" 19 "sync/atomic" 20 ) 21 22 // Cache implements an in-memory cache with FIFO-based eviction 23 // it's mostly like the S3-fifo, only without the ghost queue part 24 type Cache[K comparable, V any] struct { 25 capacity int 26 capacity1 int 27 onEvict func(K, V) 28 keyShardFunc func(K) uint8 29 30 shards [256]struct { 31 sync.RWMutex 32 values map[K]*_CacheItem[K, V] 33 } 34 35 queueLock sync.Mutex 36 used1 int 37 queue1 Queue[*_CacheItem[K, V]] 38 used2 int 39 queue2 Queue[*_CacheItem[K, V]] 40 } 41 42 type _CacheItem[K comparable, V any] struct { 43 key K 44 value V 45 size int 46 count atomic.Int32 47 } 48 49 func (c *_CacheItem[K, V]) inc() { 50 for { 51 cur := c.count.Load() 52 if cur >= 3 { 53 return 54 } 55 if c.count.CompareAndSwap(cur, cur+1) { 56 return 57 } 58 } 59 } 60 61 func (c *_CacheItem[K, V]) dec() { 62 for { 63 cur := c.count.Load() 64 if cur <= 0 { 65 return 66 } 67 if c.count.CompareAndSwap(cur, cur-1) { 68 return 69 } 70 } 71 } 72 73 func New[K comparable, V any]( 74 capacity int, 75 onEvict func(K, V), 76 keyShardFunc func(K) uint8, 77 ) *Cache[K, V] { 78 ret := &Cache[K, V]{ 79 capacity: capacity, 80 capacity1: capacity / 10, 81 queue1: *NewQueue[*_CacheItem[K, V]](), 82 queue2: *NewQueue[*_CacheItem[K, V]](), 83 onEvict: onEvict, 84 keyShardFunc: keyShardFunc, 85 } 86 for i := range ret.shards { 87 ret.shards[i].values = make(map[K]*_CacheItem[K, V], 1024) 88 } 89 return ret 90 } 91 92 func (c *Cache[K, V]) Set(key K, value V, size int) { 93 shard := &c.shards[c.keyShardFunc(key)] 94 shard.Lock() 95 _, ok := shard.values[key] 96 if ok { 97 // existed 98 shard.Unlock() 99 return 100 } 101 102 item := &_CacheItem[K, V]{ 103 key: key, 104 value: value, 105 size: size, 106 } 107 shard.values[key] = item 108 shard.Unlock() 109 110 c.queueLock.Lock() 111 defer c.queueLock.Unlock() 112 c.queue1.enqueue(item) 113 c.used1 += size 114 if c.used1+c.used2 > c.capacity { 115 c.evict() 116 } 117 } 118 119 func (c *Cache[K, V]) Get(key K) (value V, ok bool) { 120 shard := &c.shards[c.keyShardFunc(key)] 121 shard.RLock() 122 var item *_CacheItem[K, V] 123 item, ok = shard.values[key] 124 if !ok { 125 shard.RUnlock() 126 return 127 } 128 shard.RUnlock() 129 item.inc() 130 return item.value, true 131 } 132 133 func (c *Cache[K, V]) Delete(key K) { 134 shard := &c.shards[c.keyShardFunc(key)] 135 shard.Lock() 136 defer shard.Unlock() 137 delete(shard.values, key) 138 // we don't update queues 139 } 140 141 func (c *Cache[K, V]) evict() { 142 for c.used1+c.used2 > c.capacity { 143 if c.used1 > c.capacity1 { 144 c.evict1() 145 } else { 146 c.evict2() 147 } 148 } 149 } 150 151 func (c *Cache[K, V]) evict1() { 152 // queue 1 153 for { 154 item, ok := c.queue1.dequeue() 155 if !ok { 156 // queue empty 157 return 158 } 159 if item.count.Load() > 1 { 160 // put queue2 161 c.queue2.enqueue(item) 162 c.used1 -= item.size 163 c.used2 += item.size 164 } else { 165 // evict 166 shard := &c.shards[c.keyShardFunc(item.key)] 167 shard.Lock() 168 delete(shard.values, item.key) 169 shard.Unlock() 170 if c.onEvict != nil { 171 c.onEvict(item.key, item.value) 172 } 173 c.used1 -= item.size 174 return 175 } 176 } 177 } 178 179 func (c *Cache[K, V]) evict2() { 180 // queue 2 181 for { 182 item, ok := c.queue2.dequeue() 183 if !ok { 184 // empty queue 185 break 186 } 187 if item.count.Load() > 0 { 188 // re-enqueue 189 c.queue2.enqueue(item) 190 item.dec() 191 } else { 192 // evict 193 shard := &c.shards[c.keyShardFunc(item.key)] 194 shard.Lock() 195 delete(shard.values, item.key) 196 shard.Unlock() 197 if c.onEvict != nil { 198 c.onEvict(item.key, item.value) 199 } 200 c.used2 -= item.size 201 return 202 } 203 } 204 }