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  }