github.com/Schaudge/hts@v0.0.0-20240223063651-737b4d69d68c/bgzf/cache/cache.go (about) 1 // Copyright ©2015 The bíogo Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Package cache provides basic block cache types for the bgzf package. 6 package cache 7 8 import ( 9 "sync" 10 11 "github.com/Schaudge/hts/bgzf" 12 ) 13 14 var ( 15 _ Cache = (*LRU)(nil) 16 _ Cache = (*FIFO)(nil) 17 _ Cache = (*Random)(nil) 18 ) 19 20 // Free attempts to drop as many blocks from c as needed allow 21 // n successful Put calls on c. It returns a boolean indicating 22 // whether n slots were made available. 23 func Free(n int, c Cache) bool { 24 empty := c.Cap() - c.Len() 25 if n <= empty { 26 return true 27 } 28 c.Drop(n - empty) 29 return c.Cap()-c.Len() >= n 30 } 31 32 // Cache is an extension of bgzf.Cache that allows inspection 33 // and manipulation of the cache. 34 type Cache interface { 35 bgzf.Cache 36 37 // Len returns the number of elements held by 38 // the cache. 39 Len() int 40 41 // Cap returns the maximum number of elements 42 // that can be held by the cache. 43 Cap() int 44 45 // Resize changes the capacity of the cache to n, 46 // dropping excess blocks if n is less than the 47 // number of cached blocks. 48 Resize(n int) 49 50 // Drop evicts n elements from the cache according 51 // to the cache eviction policy. 52 Drop(n int) 53 } 54 55 func insertAfter(pos, n *node) { 56 n.prev = pos 57 pos.next, n.next, pos.next.prev = n, pos.next, n 58 } 59 60 func remove(n *node, table map[int64]*node) { 61 delete(table, n.b.Base()) 62 n.prev.next = n.next 63 n.next.prev = n.prev 64 n.next = nil 65 n.prev = nil 66 } 67 68 // NewLRU returns an LRU cache with n slots. If n is less than 1 69 // a nil cache is returned. 70 func NewLRU(n int) Cache { 71 if n < 1 { 72 return nil 73 } 74 c := LRU{ 75 table: make(map[int64]*node, n), 76 cap: n, 77 } 78 c.root.next = &c.root 79 c.root.prev = &c.root 80 return &c 81 } 82 83 // LRU satisfies the Cache interface with least recently used eviction 84 // behavior where Unused Blocks are preferentially evicted. 85 type LRU struct { 86 mu sync.RWMutex 87 root node 88 table map[int64]*node 89 cap int 90 } 91 92 type node struct { 93 b bgzf.Block 94 95 next, prev *node 96 } 97 98 // Len returns the number of elements held by the cache. 99 func (c *LRU) Len() int { 100 c.mu.RLock() 101 defer c.mu.RUnlock() 102 103 return len(c.table) 104 } 105 106 // Cap returns the maximum number of elements that can be held by the cache. 107 func (c *LRU) Cap() int { 108 c.mu.RLock() 109 defer c.mu.RUnlock() 110 111 return c.cap 112 } 113 114 // Resize changes the capacity of the cache to n, dropping excess blocks 115 // if n is less than the number of cached blocks. 116 func (c *LRU) Resize(n int) { 117 c.mu.Lock() 118 if n < len(c.table) { 119 c.drop(len(c.table) - n) 120 } 121 c.cap = n 122 c.mu.Unlock() 123 } 124 125 // Drop evicts n elements from the cache according to the cache eviction policy. 126 func (c *LRU) Drop(n int) { 127 c.mu.Lock() 128 c.drop(n) 129 c.mu.Unlock() 130 } 131 132 func (c *LRU) drop(n int) { 133 for ; n > 0 && c.Len() > 0; n-- { 134 remove(c.root.prev, c.table) 135 } 136 } 137 138 // Get returns the Block in the Cache with the specified base or a nil Block 139 // if it does not exist. 140 func (c *LRU) Get(base int64) bgzf.Block { 141 c.mu.Lock() 142 defer c.mu.Unlock() 143 144 n, ok := c.table[base] 145 if !ok { 146 return nil 147 } 148 remove(n, c.table) 149 return n.b 150 } 151 152 // Peek returns a boolean indicating whether a Block exists in the Cache for 153 // the given base offset and the expected offset for the subsequent Block in 154 // the BGZF stream. 155 func (c *LRU) Peek(base int64) (exist bool, next int64) { 156 c.mu.RLock() 157 defer c.mu.RUnlock() 158 159 n, exist := c.table[base] 160 if !exist { 161 return false, -1 162 } 163 next = n.b.NextBase() 164 return exist, next 165 } 166 167 // Put inserts a Block into the Cache, returning the Block that was evicted or 168 // nil if no eviction was necessary and the Block was retained. Unused Blocks 169 // are not retained but are returned if the Cache is full. 170 func (c *LRU) Put(b bgzf.Block) (evicted bgzf.Block, retained bool) { 171 c.mu.Lock() 172 defer c.mu.Unlock() 173 174 var d bgzf.Block 175 if _, ok := c.table[b.Base()]; ok { 176 return b, false 177 } 178 used := b.Used() 179 if len(c.table) == c.cap { 180 if !used { 181 return b, false 182 } 183 d = c.root.prev.b 184 remove(c.root.prev, c.table) 185 } 186 n := &node{b: b} 187 c.table[b.Base()] = n 188 if used { 189 insertAfter(&c.root, n) 190 } else { 191 insertAfter(c.root.prev, n) 192 } 193 return d, true 194 } 195 196 // NewFIFO returns a FIFO cache with n slots. If n is less than 1 197 // a nil cache is returned. 198 func NewFIFO(n int) Cache { 199 if n < 1 { 200 return nil 201 } 202 c := FIFO{ 203 table: make(map[int64]*node, n), 204 cap: n, 205 } 206 c.root.next = &c.root 207 c.root.prev = &c.root 208 return &c 209 } 210 211 // FIFO satisfies the Cache interface with first in first out eviction 212 // behavior where Unused Blocks are preferentially evicted. 213 type FIFO struct { 214 mu sync.RWMutex 215 root node 216 table map[int64]*node 217 cap int 218 } 219 220 // Len returns the number of elements held by the cache. 221 func (c *FIFO) Len() int { 222 c.mu.RLock() 223 defer c.mu.RUnlock() 224 225 return len(c.table) 226 } 227 228 // Cap returns the maximum number of elements that can be held by the cache. 229 func (c *FIFO) Cap() int { 230 c.mu.RLock() 231 defer c.mu.RUnlock() 232 233 return c.cap 234 } 235 236 // Resize changes the capacity of the cache to n, dropping excess blocks 237 // if n is less than the number of cached blocks. 238 func (c *FIFO) Resize(n int) { 239 c.mu.Lock() 240 if n < len(c.table) { 241 c.drop(len(c.table) - n) 242 } 243 c.cap = n 244 c.mu.Unlock() 245 } 246 247 // Drop evicts n elements from the cache according to the cache eviction policy. 248 func (c *FIFO) Drop(n int) { 249 c.mu.Lock() 250 c.drop(n) 251 c.mu.Unlock() 252 } 253 254 func (c *FIFO) drop(n int) { 255 for ; n > 0 && c.Len() > 0; n-- { 256 remove(c.root.prev, c.table) 257 } 258 } 259 260 // Get returns the Block in the Cache with the specified base or a nil Block 261 // if it does not exist. 262 func (c *FIFO) Get(base int64) bgzf.Block { 263 c.mu.Lock() 264 defer c.mu.Unlock() 265 266 n, ok := c.table[base] 267 if !ok { 268 return nil 269 } 270 if !n.b.Used() { 271 remove(n, c.table) 272 } 273 return n.b 274 } 275 276 // Peek returns a boolean indicating whether a Block exists in the Cache for 277 // the given base offset and the expected offset for the subsequent Block in 278 // the BGZF stream. 279 func (c *FIFO) Peek(base int64) (exist bool, next int64) { 280 c.mu.RLock() 281 defer c.mu.RUnlock() 282 283 n, exist := c.table[base] 284 if !exist { 285 return false, -1 286 } 287 next = n.b.NextBase() 288 return exist, next 289 } 290 291 // Put inserts a Block into the Cache, returning the Block that was evicted or 292 // nil if no eviction was necessary and the Block was retained. Unused Blocks 293 // are not retained but are returned if the Cache is full. 294 func (c *FIFO) Put(b bgzf.Block) (evicted bgzf.Block, retained bool) { 295 c.mu.Lock() 296 defer c.mu.Unlock() 297 298 var d bgzf.Block 299 if _, ok := c.table[b.Base()]; ok { 300 return b, false 301 } 302 used := b.Used() 303 if len(c.table) == c.cap { 304 if !used { 305 return b, false 306 } 307 d = c.root.prev.b 308 remove(c.root.prev, c.table) 309 } 310 n := &node{b: b} 311 c.table[b.Base()] = n 312 if used { 313 insertAfter(&c.root, n) 314 } else { 315 insertAfter(c.root.prev, n) 316 } 317 return d, true 318 } 319 320 // NewRandom returns a random eviction cache with n slots. If n is less than 1 321 // a nil cache is returned. 322 func NewRandom(n int) Cache { 323 if n < 1 { 324 return nil 325 } 326 return &Random{ 327 table: make(map[int64]bgzf.Block, n), 328 cap: n, 329 } 330 } 331 332 // Random satisfies the Cache interface with random eviction behavior 333 // where Unused Blocks are preferentially evicted. 334 type Random struct { 335 mu sync.RWMutex 336 table map[int64]bgzf.Block 337 cap int 338 } 339 340 // Len returns the number of elements held by the cache. 341 func (c *Random) Len() int { 342 c.mu.RLock() 343 defer c.mu.RUnlock() 344 345 return len(c.table) 346 } 347 348 // Cap returns the maximum number of elements that can be held by the cache. 349 func (c *Random) Cap() int { 350 c.mu.RLock() 351 defer c.mu.RUnlock() 352 353 return c.cap 354 } 355 356 // Resize changes the capacity of the cache to n, dropping excess blocks 357 // if n is less than the number of cached blocks. 358 func (c *Random) Resize(n int) { 359 c.mu.Lock() 360 if n < len(c.table) { 361 c.drop(len(c.table) - n) 362 } 363 c.cap = n 364 c.mu.Unlock() 365 } 366 367 // Drop evicts n elements from the cache according to the cache eviction policy. 368 func (c *Random) Drop(n int) { 369 c.mu.Lock() 370 c.drop(n) 371 c.mu.Unlock() 372 } 373 374 func (c *Random) drop(n int) { 375 if n < 1 { 376 return 377 } 378 for k, b := range c.table { 379 if b.Used() { 380 continue 381 } 382 delete(c.table, k) 383 if n--; n == 0 { 384 return 385 } 386 } 387 for k := range c.table { 388 delete(c.table, k) 389 if n--; n == 0 { 390 break 391 } 392 } 393 } 394 395 // Get returns the Block in the Cache with the specified base or a nil Block 396 // if it does not exist. 397 func (c *Random) Get(base int64) bgzf.Block { 398 c.mu.Lock() 399 defer c.mu.Unlock() 400 401 b, ok := c.table[base] 402 if !ok { 403 return nil 404 } 405 delete(c.table, base) 406 return b 407 } 408 409 // Peek returns a boolean indicating whether a Block exists in the Cache for 410 // the given base offset and the expected offset for the subsequent Block in 411 // the BGZF stream. 412 func (c *Random) Peek(base int64) (exist bool, next int64) { 413 c.mu.RLock() 414 defer c.mu.RUnlock() 415 416 n, exist := c.table[base] 417 if !exist { 418 return false, -1 419 } 420 next = n.NextBase() 421 return exist, next 422 } 423 424 // Put inserts a Block into the Cache, returning the Block that was evicted or 425 // nil if no eviction was necessary and the Block was retained. Unused Blocks 426 // are not retained but are returned if the Cache is full. 427 func (c *Random) Put(b bgzf.Block) (evicted bgzf.Block, retained bool) { 428 c.mu.Lock() 429 defer c.mu.Unlock() 430 431 var d bgzf.Block 432 if _, ok := c.table[b.Base()]; ok { 433 return b, false 434 } 435 if len(c.table) == c.cap { 436 if !b.Used() { 437 return b, false 438 } 439 for k, v := range c.table { 440 if v.Used() { 441 continue 442 } 443 delete(c.table, k) 444 d = v 445 goto done 446 } 447 for k, v := range c.table { 448 delete(c.table, k) 449 d = v 450 break 451 } 452 done: 453 } 454 c.table[b.Base()] = b 455 return d, true 456 } 457 458 // StatsRecorder allows a bgzf.Cache to capture cache statistics. 459 type StatsRecorder struct { 460 bgzf.Cache 461 462 mu sync.RWMutex 463 stats Stats 464 } 465 466 // Stats represents statistics of a bgzf.Cache. 467 type Stats struct { 468 Gets int // number of Get operations 469 Misses int // number of cache misses 470 Puts int // number of Put operations 471 Retains int // number of times a Put has resulted in Block retention 472 Evictions int // number of times a Put has resulted in a Block eviction 473 } 474 475 // Stats returns the current statistics for the cache. 476 func (s *StatsRecorder) Stats() Stats { 477 s.mu.RLock() 478 defer s.mu.RUnlock() 479 return s.stats 480 } 481 482 // Reset zeros the statistics kept by the StatsRecorder. 483 func (s *StatsRecorder) Reset() { 484 s.mu.Lock() 485 s.stats = Stats{} 486 s.mu.Unlock() 487 } 488 489 // Get returns the Block in the underlying Cache with the specified base or a nil 490 // Block if it does not exist. It updates the gets and misses statistics. 491 func (s *StatsRecorder) Get(base int64) bgzf.Block { 492 s.mu.Lock() 493 s.stats.Gets++ 494 blk := s.Cache.Get(base) 495 if blk == nil { 496 s.stats.Misses++ 497 } 498 s.mu.Unlock() 499 return blk 500 } 501 502 // Put inserts a Block into the underlying Cache, returning the Block and eviction 503 // status according to the underlying cache behavior. It updates the puts, retains and 504 // evictions statistics. 505 func (s *StatsRecorder) Put(b bgzf.Block) (evicted bgzf.Block, retained bool) { 506 s.mu.Lock() 507 s.stats.Puts++ 508 blk, retained := s.Cache.Put(b) 509 if retained { 510 s.stats.Retains++ 511 if blk != nil { 512 s.stats.Evictions++ 513 } 514 } 515 s.mu.Unlock() 516 return blk, retained 517 }