github.com/petermattis/pebble@v0.0.0-20190905164901-ab51a2166067/cache/clockpro.go (about) 1 // Copyright 2018. All rights reserved. Use of this source code is governed by 2 // an MIT-style license that can be found in the LICENSE file. 3 4 // Package cache implements the CLOCK-Pro caching algorithm. 5 /* 6 7 CLOCK-Pro is a patent-free alternative to the Adaptive Replacement Cache, 8 https://en.wikipedia.org/wiki/Adaptive_replacement_cache. 9 It is an approximation of LIRS ( https://en.wikipedia.org/wiki/LIRS_caching_algorithm ), 10 much like the CLOCK page replacement algorithm is an approximation of LRU. 11 12 This implementation is based on the python code from https://bitbucket.org/SamiLehtinen/pyclockpro . 13 14 Slides describing the algorithm: http://fr.slideshare.net/huliang64/clockpro 15 16 The original paper: http://static.usenix.org/event/usenix05/tech/general/full_papers/jiang/jiang_html/html.html 17 18 It is MIT licensed, like the original. 19 */ 20 package cache // import "github.com/petermattis/pebble/cache" 21 22 import ( 23 "runtime" 24 "sync" 25 "sync/atomic" 26 "unsafe" 27 ) 28 29 type entryType int8 30 31 const ( 32 etTest entryType = iota 33 etCold 34 etHot 35 ) 36 37 func (p entryType) String() string { 38 switch p { 39 case etTest: 40 return "test" 41 case etCold: 42 return "cold" 43 case etHot: 44 return "hot" 45 } 46 return "unknown" 47 } 48 49 type fileKey struct { 50 dbNum uint64 51 fileNum uint64 52 } 53 54 type key struct { 55 fileKey 56 offset uint64 57 } 58 59 type value struct { 60 buf []byte 61 refs int32 62 } 63 64 func newValue(b []byte) *value { 65 if b == nil { 66 return nil 67 } 68 // A value starts with 2 references. One for the cache, and one for the 69 // handle that will be returned. 70 return &value{buf: b, refs: 2} 71 } 72 73 func (v *value) acquire() { 74 atomic.AddInt32(&v.refs, 1) 75 } 76 77 func (v *value) release() bool { 78 return atomic.AddInt32(&v.refs, -1) == 0 79 } 80 81 type entry struct { 82 key key 83 val unsafe.Pointer 84 blockLink struct { 85 next *entry 86 prev *entry 87 } 88 fileLink struct { 89 next *entry 90 prev *entry 91 } 92 size int64 93 ptype entryType 94 ref int32 95 } 96 97 func (e *entry) init() *entry { 98 e.blockLink.next = e 99 e.blockLink.prev = e 100 e.fileLink.next = e 101 e.fileLink.prev = e 102 return e 103 } 104 105 func (e *entry) next() *entry { 106 if e == nil { 107 return nil 108 } 109 return e.blockLink.next 110 } 111 112 func (e *entry) prev() *entry { 113 if e == nil { 114 return nil 115 } 116 return e.blockLink.prev 117 } 118 119 func (e *entry) link(s *entry) { 120 s.blockLink.prev = e.blockLink.prev 121 s.blockLink.prev.blockLink.next = s 122 s.blockLink.next = e 123 s.blockLink.next.blockLink.prev = s 124 } 125 126 func (e *entry) unlink() *entry { 127 next := e.blockLink.next 128 e.blockLink.prev.blockLink.next = e.blockLink.next 129 e.blockLink.next.blockLink.prev = e.blockLink.prev 130 e.blockLink.prev = e 131 e.blockLink.next = e 132 return next 133 } 134 135 func (e *entry) linkFile(s *entry) { 136 s.fileLink.prev = e.fileLink.prev 137 s.fileLink.prev.fileLink.next = s 138 s.fileLink.next = e 139 s.fileLink.next.fileLink.prev = s 140 } 141 142 func (e *entry) unlinkFile() *entry { 143 next := e.fileLink.next 144 e.fileLink.prev.fileLink.next = e.fileLink.next 145 e.fileLink.next.fileLink.prev = e.fileLink.prev 146 e.fileLink.prev = e 147 e.fileLink.next = e 148 return next 149 } 150 151 func (e *entry) setValue(v *value, free func([]byte)) { 152 if old := e.getValue(); old != nil { 153 if old.release() && free != nil { 154 free(old.buf) 155 } 156 } 157 atomic.StorePointer(&e.val, unsafe.Pointer(v)) 158 } 159 160 func (e *entry) getValue() *value { 161 return (*value)(atomic.LoadPointer(&e.val)) 162 } 163 164 func (e *entry) Get() []byte { 165 v := e.getValue() 166 if v == nil { 167 return nil 168 } 169 atomic.StoreInt32(&e.ref, 1) 170 return v.buf 171 } 172 173 // Handle provides a strong reference to an entry in the cache. The reference 174 // does not pin the entry in the cache, but it does prevent the underlying byte 175 // slice from being reused. 176 type Handle struct { 177 entry *entry 178 value *value 179 free func([]byte) 180 } 181 182 // Get returns the value stored in handle. 183 func (h Handle) Get() []byte { 184 if h.value != nil { 185 return h.value.buf 186 } 187 return nil 188 } 189 190 // Release releases the reference to the cache entry. 191 func (h Handle) Release() { 192 if h.value != nil { 193 if h.value.release() && h.free != nil { 194 h.free(h.value.buf) 195 } 196 h.value = nil 197 } 198 } 199 200 // Weak returns a weak handle and clears the strong reference, preventing the 201 // underlying data storage from being reused. Clearing the strong reference 202 // allows the underlying data to be evicted and GC'd, but the buffer will not 203 // be reused. 204 func (h Handle) Weak() WeakHandle { 205 h.value = nil 206 if h.entry == nil { 207 return nil // return a nil interface, not (*entry)(nil) 208 } 209 return h.entry 210 } 211 212 // WeakHandle provides a "weak" reference to an entry in the cache. A weak 213 // reference allows the entry to be evicted, but also provides fast access 214 type WeakHandle interface { 215 // Get retrieves the value associated with the weak handle, returning nil if 216 // no value is present. 217 Get() []byte 218 } 219 220 type shard struct { 221 free func([]byte) 222 223 mu sync.RWMutex 224 225 maxSize int64 226 coldSize int64 227 blocks map[key]*entry // fileNum+offset -> block 228 files map[fileKey]*entry // fileNum -> list of blocks 229 230 handHot *entry 231 handCold *entry 232 handTest *entry 233 234 countHot int64 235 countCold int64 236 countTest int64 237 } 238 239 func (c *shard) Get(dbNum, fileNum, offset uint64) Handle { 240 c.mu.RLock() 241 e := c.blocks[key{fileKey{dbNum, fileNum}, offset}] 242 var value *value 243 if e != nil { 244 value = e.getValue() 245 if value != nil { 246 value.acquire() 247 atomic.StoreInt32(&e.ref, 1) 248 } else { 249 e = nil 250 } 251 } 252 c.mu.RUnlock() 253 return Handle{value: value, free: c.free} 254 } 255 256 func (c *shard) Set(dbNum, fileNum, offset uint64, value []byte) Handle { 257 c.mu.Lock() 258 defer c.mu.Unlock() 259 260 k := key{fileKey{dbNum, fileNum}, offset} 261 e := c.blocks[k] 262 v := newValue(value) 263 264 switch { 265 case e == nil: 266 // no cache entry? add it 267 e = &entry{ptype: etCold, key: k, size: int64(len(value))} 268 e.init() 269 e.setValue(v, c.free) 270 c.metaAdd(k, e) 271 c.countCold += e.size 272 273 case e.getValue() != nil: 274 // cache entry was a hot or cold page 275 e.setValue(v, c.free) 276 atomic.StoreInt32(&e.ref, 1) 277 delta := int64(len(value)) - e.size 278 e.size = int64(len(value)) 279 if e.ptype == etHot { 280 c.countHot += delta 281 } else { 282 c.countCold += delta 283 } 284 c.evict() 285 286 default: 287 // cache entry was a test page 288 c.coldSize += e.size 289 if c.coldSize > c.maxSize { 290 c.coldSize = c.maxSize 291 } 292 atomic.StoreInt32(&e.ref, 0) 293 e.setValue(v, c.free) 294 e.ptype = etHot 295 c.countTest -= e.size 296 c.metaDel(e) 297 c.metaAdd(k, e) 298 c.countHot += e.size 299 } 300 301 return Handle{entry: e, value: v, free: c.free} 302 } 303 304 // EvictFile evicts all of the cache values for the specified file. 305 func (c *shard) EvictFile(dbNum, fileNum uint64) { 306 c.mu.Lock() 307 defer c.mu.Unlock() 308 309 blocks := c.files[fileKey{dbNum, fileNum}] 310 if blocks == nil { 311 return 312 } 313 for b, n := blocks, (*entry)(nil); ; b = n { 314 switch b.ptype { 315 case etHot: 316 c.countHot -= b.size 317 case etCold: 318 c.countCold -= b.size 319 case etTest: 320 c.countTest -= b.size 321 } 322 n = b.fileLink.next 323 c.metaDel(b) 324 if b == n { 325 break 326 } 327 } 328 } 329 330 // Size returns the current space used by the cache. 331 func (c *shard) Size() int64 { 332 c.mu.Lock() 333 size := c.countHot + c.countCold 334 c.mu.Unlock() 335 return size 336 } 337 338 func (c *shard) metaAdd(key key, e *entry) { 339 c.evict() 340 341 c.blocks[key] = e 342 343 if c.handHot == nil { 344 // first element 345 c.handHot = e 346 c.handCold = e 347 c.handTest = e 348 } else { 349 c.handHot.link(e) 350 } 351 352 if c.handCold == c.handHot { 353 c.handCold = c.handCold.prev() 354 } 355 356 if fileBlocks := c.files[key.fileKey]; fileBlocks == nil { 357 c.files[key.fileKey] = e 358 } else { 359 fileBlocks.linkFile(e) 360 } 361 } 362 363 func (c *shard) metaDel(e *entry) { 364 delete(c.blocks, e.key) 365 366 if e == c.handHot { 367 c.handHot = c.handHot.prev() 368 } 369 if e == c.handCold { 370 c.handCold = c.handCold.prev() 371 } 372 if e == c.handTest { 373 c.handTest = c.handTest.prev() 374 } 375 376 if e.unlink() == e { 377 // This was the last entry in the cache. 378 c.handHot = nil 379 c.handCold = nil 380 c.handTest = nil 381 } 382 383 if next := e.unlinkFile(); e == next { 384 delete(c.files, e.key.fileKey) 385 } else { 386 c.files[e.key.fileKey] = next 387 } 388 } 389 390 func (c *shard) evict() { 391 for c.maxSize <= c.countHot+c.countCold { 392 c.runHandCold() 393 } 394 } 395 396 func (c *shard) runHandCold() { 397 if c.handCold == nil { 398 return 399 } 400 401 e := c.handCold 402 if e.ptype == etCold { 403 if atomic.LoadInt32(&e.ref) == 1 { 404 atomic.StoreInt32(&e.ref, 0) 405 e.ptype = etHot 406 c.countCold -= e.size 407 c.countHot += e.size 408 } else { 409 e.setValue(nil, c.free) 410 e.ptype = etTest 411 c.countCold -= e.size 412 c.countTest += e.size 413 for c.maxSize < c.countTest { 414 c.runHandTest() 415 } 416 } 417 } 418 419 c.handCold = c.handCold.next() 420 421 for c.maxSize-c.coldSize <= c.countHot { 422 c.runHandHot() 423 } 424 } 425 426 func (c *shard) runHandHot() { 427 if c.handHot == c.handTest { 428 c.runHandTest() 429 } 430 if c.handHot == nil { 431 return 432 } 433 434 e := c.handHot 435 if e.ptype == etHot { 436 if atomic.LoadInt32(&e.ref) == 1 { 437 atomic.StoreInt32(&e.ref, 0) 438 } else { 439 e.ptype = etCold 440 c.countHot -= e.size 441 c.countCold += e.size 442 } 443 } 444 445 c.handHot = c.handHot.next() 446 } 447 448 func (c *shard) runHandTest() { 449 if c.countCold > 0 && c.handTest == c.handCold { 450 c.runHandCold() 451 } 452 if c.handTest == nil { 453 return 454 } 455 456 e := c.handTest 457 if e.ptype == etTest { 458 prev := c.handTest.prev() 459 c.metaDel(c.handTest) 460 c.handTest = prev 461 462 c.countTest -= e.size 463 c.coldSize -= e.size 464 if c.coldSize < 0 { 465 c.coldSize = 0 466 } 467 } 468 469 c.handTest = c.handTest.next() 470 } 471 472 // Cache ... 473 type Cache struct { 474 maxSize int64 475 shards []shard 476 allocPool *sync.Pool 477 } 478 479 // New creates a new cache of the specified size. Memory for the cache is 480 // allocated on demand, not during initialization. 481 func New(size int64) *Cache { 482 return newShards(size, 2*runtime.NumCPU()) 483 } 484 485 func newShards(size int64, shards int) *Cache { 486 c := &Cache{ 487 maxSize: size, 488 shards: make([]shard, shards), 489 allocPool: &sync.Pool{ 490 New: func() interface{} { 491 return &allocCache{} 492 }, 493 }, 494 } 495 free := c.Free 496 for i := range c.shards { 497 c.shards[i] = shard{ 498 free: free, 499 maxSize: size / int64(len(c.shards)), 500 coldSize: size / int64(len(c.shards)), 501 blocks: make(map[key]*entry), 502 files: make(map[fileKey]*entry), 503 } 504 } 505 return c 506 } 507 508 func (c *Cache) getShard(dbNum, fileNum, offset uint64) *shard { 509 // Inlined version of fnv.New64 + Write. 510 const offset64 = 14695981039346656037 511 const prime64 = 1099511628211 512 513 h := uint64(offset64) 514 for i := 0; i < 8; i++ { 515 h *= prime64 516 h ^= uint64(dbNum & 0xff) 517 dbNum >>= 8 518 } 519 for i := 0; i < 8; i++ { 520 h *= prime64 521 h ^= uint64(fileNum & 0xff) 522 fileNum >>= 8 523 } 524 for i := 0; i < 8; i++ { 525 h *= prime64 526 h ^= uint64(offset & 0xff) 527 offset >>= 8 528 } 529 530 return &c.shards[h%uint64(len(c.shards))] 531 } 532 533 // Get retrieves the cache value for the specified file and offset, returning 534 // nil if no value is present. 535 func (c *Cache) Get(dbNum, fileNum, offset uint64) Handle { 536 return c.getShard(dbNum, fileNum, offset).Get(dbNum, fileNum, offset) 537 } 538 539 // Set sets the cache value for the specified file and offset, overwriting an 540 // existing value if present. A Handle is returned which provides faster 541 // retrieval of the cached value than Get (lock-free and avoidance of the map 542 // lookup). 543 func (c *Cache) Set(dbNum, fileNum, offset uint64, value []byte) Handle { 544 return c.getShard(dbNum, fileNum, offset).Set(dbNum, fileNum, offset, value) 545 } 546 547 // EvictFile evicts all of the cache values for the specified file. 548 func (c *Cache) EvictFile(dbNum, fileNum uint64) { 549 for i := range c.shards { 550 c.shards[i].EvictFile(dbNum, fileNum) 551 } 552 } 553 554 // MaxSize returns the max size of the cache. 555 func (c *Cache) MaxSize() int64 { 556 return c.maxSize 557 } 558 559 // Size returns the current space used by the cache. 560 func (c *Cache) Size() int64 { 561 var size int64 562 for i := range c.shards { 563 size += c.shards[i].Size() 564 } 565 return size 566 } 567 568 // Alloc allocates a byte slice of the specified size, possibly reusing 569 // previously allocated but unused memory. 570 func (c *Cache) Alloc(n int) []byte { 571 a := c.allocPool.Get().(*allocCache) 572 b := a.alloc(n) 573 c.allocPool.Put(a) 574 return b 575 } 576 577 // Free frees the specified slice of memory. The buffer will possibly be 578 // reused, making it invalid to use the buffer after calling Free. 579 func (c *Cache) Free(b []byte) { 580 a := c.allocPool.Get().(*allocCache) 581 a.free(b) 582 c.allocPool.Put(a) 583 }