github.com/phuslu/lru@v1.0.16-0.20240421170520-46288a2fd47c/ttl_shard.go (about) 1 // Copyright 2023-2024 Phus Lu. All rights reserved. 2 3 package lru 4 5 import ( 6 "sync" 7 "sync/atomic" 8 "time" 9 "unsafe" 10 ) 11 12 // ttlnode is a list of ttl node, storing key-value pairs and related information 13 type ttlnode[K comparable, V any] struct { 14 key K 15 expires uint32 16 next uint32 17 prev uint32 18 ttl uint32 19 value V 20 } 21 22 type ttlbucket struct { 23 hdib uint32 // bitfield { hash:24 dib:8 } 24 index uint32 // node index 25 } 26 27 // ttlshard is a LRU partition contains a list and a hash table. 28 type ttlshard[K comparable, V any] struct { 29 mu sync.Mutex 30 31 // the hash table, with 20% extra space than the list for fewer conflicts. 32 table_buckets []uint64 // []ttlbucket 33 table_mask uint32 34 table_length uint32 35 table_hasher func(key unsafe.Pointer, seed uintptr) uintptr 36 table_seed uintptr 37 38 // the list of nodes 39 list []ttlnode[K, V] 40 41 sliding bool 42 43 // stats 44 stats_getcalls uint64 45 stats_setcalls uint64 46 stats_misses uint64 47 48 // padding 49 _ [16]byte 50 } 51 52 func (s *ttlshard[K, V]) Init(size uint32, hasher func(key unsafe.Pointer, seed uintptr) uintptr, seed uintptr) { 53 s.list_Init(size) 54 s.table_Init(size, hasher, seed) 55 } 56 57 func (s *ttlshard[K, V]) Get(hash uint32, key K) (value V, ok bool) { 58 s.mu.Lock() 59 60 s.stats_getcalls++ 61 62 if index, exists := s.table_Get(hash, key); exists { 63 if expires := s.list[index].expires; expires == 0 { 64 s.list_MoveToFront(index) 65 // value = s.list[index].value 66 value = (*ttlnode[K, V])(unsafe.Add(unsafe.Pointer(&s.list[0]), uintptr(index)*unsafe.Sizeof(s.list[0]))).value 67 ok = true 68 } else if now := atomic.LoadUint32(&clock); now < expires { 69 if s.sliding { 70 s.list[index].expires = now + s.list[index].ttl 71 } 72 s.list_MoveToFront(index) 73 // value = s.list[index].value 74 value = (*ttlnode[K, V])(unsafe.Add(unsafe.Pointer(&s.list[0]), uintptr(index)*unsafe.Sizeof(s.list[0]))).value 75 ok = true 76 } else { 77 s.list_MoveToBack(index) 78 // s.list[index].value = value 79 (*ttlnode[K, V])(unsafe.Add(unsafe.Pointer(&s.list[0]), uintptr(index)*unsafe.Sizeof(s.list[0]))).value = value 80 s.table_Delete(hash, key) 81 s.stats_misses++ 82 } 83 } else { 84 s.stats_misses++ 85 } 86 87 s.mu.Unlock() 88 89 return 90 } 91 92 func (s *ttlshard[K, V]) Peek(hash uint32, key K) (value V, expires int64, ok bool) { 93 s.mu.Lock() 94 95 if index, exists := s.table_Get(hash, key); exists { 96 value = s.list[index].value 97 if e := s.list[index].expires; e > 0 { 98 expires = (int64(e) + clockBase) * int64(time.Second) 99 } 100 ok = true 101 } 102 103 s.mu.Unlock() 104 105 return 106 } 107 108 func (s *ttlshard[K, V]) SetIfAbsent(hash uint32, key K, value V, ttl time.Duration) (prev V, replaced bool) { 109 s.mu.Lock() 110 111 if index, exists := s.table_Get(hash, key); exists { 112 // node := &s.list[index] 113 node := (*ttlnode[K, V])(unsafe.Add(unsafe.Pointer(&s.list[0]), uintptr(index)*unsafe.Sizeof(s.list[0]))) 114 prev = node.value 115 if node.expires == 0 || atomic.LoadUint32(&clock) < node.expires { 116 s.mu.Unlock() 117 return 118 } 119 120 s.stats_setcalls++ 121 122 node.value = value 123 if ttl > 0 { 124 node.ttl = uint32(ttl / time.Second) 125 node.expires = atomic.LoadUint32(&clock) + node.ttl 126 } else { 127 node.ttl = 0 128 node.expires = 0 129 } 130 replaced = true 131 132 s.mu.Unlock() 133 return 134 } 135 136 s.stats_setcalls++ 137 138 // index := s.list_Back() 139 // node := &s.list[index] 140 index := s.list[0].prev 141 node := (*ttlnode[K, V])(unsafe.Add(unsafe.Pointer(&s.list[0]), uintptr(index)*unsafe.Sizeof(s.list[0]))) 142 evictedValue := node.value 143 s.table_Delete(uint32(s.table_hasher(noescape(unsafe.Pointer(&node.key)), s.table_seed)), node.key) 144 145 node.key = key 146 node.value = value 147 if ttl > 0 { 148 node.ttl = uint32(ttl / time.Second) 149 node.expires = atomic.LoadUint32(&clock) + node.ttl 150 } 151 s.table_Set(hash, key, index) 152 s.list_MoveToFront(index) 153 prev = evictedValue 154 155 s.mu.Unlock() 156 return 157 } 158 159 func (s *ttlshard[K, V]) Set(hash uint32, key K, value V, ttl time.Duration) (prev V, replaced bool) { 160 s.mu.Lock() 161 162 s.stats_setcalls++ 163 164 if index, exists := s.table_Get(hash, key); exists { 165 // node := &s.list[index] 166 node := (*ttlnode[K, V])(unsafe.Add(unsafe.Pointer(&s.list[0]), uintptr(index)*unsafe.Sizeof(s.list[0]))) 167 previousValue := node.value 168 s.list_MoveToFront(index) 169 node.value = value 170 if ttl > 0 { 171 node.ttl = uint32(ttl / time.Second) 172 node.expires = atomic.LoadUint32(&clock) + node.ttl 173 } 174 prev = previousValue 175 replaced = true 176 177 s.mu.Unlock() 178 return 179 } 180 181 // index := s.list_Back() 182 // node := &s.list[index] 183 index := s.list[0].prev 184 node := (*ttlnode[K, V])(unsafe.Add(unsafe.Pointer(&s.list[0]), uintptr(index)*unsafe.Sizeof(s.list[0]))) 185 evictedValue := node.value 186 if key != node.key { 187 s.table_Delete(uint32(s.table_hasher(noescape(unsafe.Pointer(&node.key)), s.table_seed)), node.key) 188 } 189 190 node.key = key 191 node.value = value 192 if ttl > 0 { 193 node.ttl = uint32(ttl / time.Second) 194 node.expires = atomic.LoadUint32(&clock) + node.ttl 195 } 196 s.table_Set(hash, key, index) 197 s.list_MoveToFront(index) 198 prev = evictedValue 199 200 s.mu.Unlock() 201 return 202 } 203 204 func (s *ttlshard[K, V]) Delete(hash uint32, key K) (v V) { 205 s.mu.Lock() 206 207 if index, exists := s.table_Get(hash, key); exists { 208 node := &s.list[index] 209 value := node.value 210 s.list_MoveToBack(index) 211 node.value = v 212 s.table_Delete(hash, key) 213 v = value 214 } 215 216 s.mu.Unlock() 217 218 return 219 } 220 221 func (s *ttlshard[K, V]) Len() (n uint32) { 222 s.mu.Lock() 223 // inlining s.table_Len() 224 n = s.table_length 225 s.mu.Unlock() 226 227 return 228 } 229 230 func (s *ttlshard[K, V]) AppendKeys(dst []K, now uint32) []K { 231 s.mu.Lock() 232 for _, bucket := range s.table_buckets { 233 b := (*ttlbucket)(unsafe.Pointer(&bucket)) 234 if b.index == 0 { 235 continue 236 } 237 node := &s.list[b.index] 238 if expires := node.expires; expires == 0 || now <= expires { 239 dst = append(dst, node.key) 240 } 241 } 242 s.mu.Unlock() 243 244 return dst 245 }