vitess.io/vitess@v0.16.2/go/cache/lru_cache.go (about)

     1  /*
     2  Copyright 2019 The Vitess Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  // Package cache implements a LRU cache.
    18  //
    19  // The implementation borrows heavily from SmallLRUCache
    20  // (originally by Nathan Schrenk). The object maintains a doubly-linked list of
    21  // elements. When an element is accessed, it is promoted to the head of the
    22  // list. When space is needed, the element at the tail of the list
    23  // (the least recently used element) is evicted.
    24  package cache
    25  
    26  import (
    27  	"container/list"
    28  	"sync"
    29  	"time"
    30  )
    31  
    32  var _ Cache = &LRUCache{}
    33  
    34  // LRUCache is a typical LRU cache implementation.  If the cache
    35  // reaches the capacity, the least recently used item is deleted from
    36  // the cache. Note the capacity is not the number of items, but the
    37  // total sum of the CachedSize() of each item.
    38  type LRUCache struct {
    39  	mu sync.Mutex
    40  
    41  	// list & table contain *entry objects.
    42  	list  *list.List
    43  	table map[string]*list.Element
    44  	cost  func(any) int64
    45  
    46  	size      int64
    47  	capacity  int64
    48  	evictions int64
    49  	hits      int64
    50  	misses    int64
    51  }
    52  
    53  // Item is what is stored in the cache
    54  type Item struct {
    55  	Key   string
    56  	Value any
    57  }
    58  
    59  type entry struct {
    60  	key          string
    61  	value        any
    62  	size         int64
    63  	timeAccessed time.Time
    64  }
    65  
    66  // NewLRUCache creates a new empty cache with the given capacity.
    67  func NewLRUCache(capacity int64, cost func(any) int64) *LRUCache {
    68  	return &LRUCache{
    69  		list:     list.New(),
    70  		table:    make(map[string]*list.Element),
    71  		capacity: capacity,
    72  		cost:     cost,
    73  	}
    74  }
    75  
    76  // Get returns a value from the cache, and marks the entry as most
    77  // recently used.
    78  func (lru *LRUCache) Get(key string) (v any, ok bool) {
    79  	lru.mu.Lock()
    80  	defer lru.mu.Unlock()
    81  
    82  	element := lru.table[key]
    83  	if element == nil {
    84  		lru.misses++
    85  		return nil, false
    86  	}
    87  	lru.moveToFront(element)
    88  	lru.hits++
    89  	return element.Value.(*entry).value, true
    90  }
    91  
    92  // Set sets a value in the cache.
    93  func (lru *LRUCache) Set(key string, value any) bool {
    94  	lru.mu.Lock()
    95  	defer lru.mu.Unlock()
    96  
    97  	if element := lru.table[key]; element != nil {
    98  		lru.updateInplace(element, value)
    99  	} else {
   100  		lru.addNew(key, value)
   101  	}
   102  	// the LRU cache cannot fail to insert items; it always returns true
   103  	return true
   104  }
   105  
   106  // Delete removes an entry from the cache, and returns if the entry existed.
   107  func (lru *LRUCache) delete(key string) bool {
   108  	lru.mu.Lock()
   109  	defer lru.mu.Unlock()
   110  
   111  	element := lru.table[key]
   112  	if element == nil {
   113  		return false
   114  	}
   115  
   116  	lru.list.Remove(element)
   117  	delete(lru.table, key)
   118  	lru.size -= element.Value.(*entry).size
   119  	return true
   120  }
   121  
   122  // Delete removes an entry from the cache
   123  func (lru *LRUCache) Delete(key string) {
   124  	lru.delete(key)
   125  }
   126  
   127  // Clear will clear the entire cache.
   128  func (lru *LRUCache) Clear() {
   129  	lru.mu.Lock()
   130  	defer lru.mu.Unlock()
   131  
   132  	lru.list.Init()
   133  	lru.table = make(map[string]*list.Element)
   134  	lru.size = 0
   135  }
   136  
   137  // Len returns the size of the cache (in entries)
   138  func (lru *LRUCache) Len() int {
   139  	lru.mu.Lock()
   140  	defer lru.mu.Unlock()
   141  	return lru.list.Len()
   142  }
   143  
   144  // SetCapacity will set the capacity of the cache. If the capacity is
   145  // smaller, and the current cache size exceed that capacity, the cache
   146  // will be shrank.
   147  func (lru *LRUCache) SetCapacity(capacity int64) {
   148  	lru.mu.Lock()
   149  	defer lru.mu.Unlock()
   150  
   151  	lru.capacity = capacity
   152  	lru.checkCapacity()
   153  }
   154  
   155  // Wait is a no-op in the LRU cache
   156  func (lru *LRUCache) Wait() {}
   157  
   158  // UsedCapacity returns the size of the cache (in bytes)
   159  func (lru *LRUCache) UsedCapacity() int64 {
   160  	return lru.size
   161  }
   162  
   163  // MaxCapacity returns the cache maximum capacity.
   164  func (lru *LRUCache) MaxCapacity() int64 {
   165  	lru.mu.Lock()
   166  	defer lru.mu.Unlock()
   167  	return lru.capacity
   168  }
   169  
   170  // Evictions returns the number of evictions
   171  func (lru *LRUCache) Evictions() int64 {
   172  	lru.mu.Lock()
   173  	defer lru.mu.Unlock()
   174  	return lru.evictions
   175  }
   176  
   177  // Hits returns number of cache hits since creation
   178  func (lru *LRUCache) Hits() int64 {
   179  	lru.mu.Lock()
   180  	defer lru.mu.Unlock()
   181  	return lru.hits
   182  }
   183  
   184  // Misses returns number of cache misses since creation
   185  func (lru *LRUCache) Misses() int64 {
   186  	lru.mu.Lock()
   187  	defer lru.mu.Unlock()
   188  	return lru.misses
   189  }
   190  
   191  // ForEach yields all the values for the cache, ordered from most recently
   192  // used to least recently used.
   193  func (lru *LRUCache) ForEach(callback func(value any) bool) {
   194  	lru.mu.Lock()
   195  	defer lru.mu.Unlock()
   196  
   197  	for e := lru.list.Front(); e != nil; e = e.Next() {
   198  		v := e.Value.(*entry)
   199  		if !callback(v.value) {
   200  			break
   201  		}
   202  	}
   203  }
   204  
   205  // Items returns all the values for the cache, ordered from most recently
   206  // used to least recently used.
   207  func (lru *LRUCache) Items() []Item {
   208  	lru.mu.Lock()
   209  	defer lru.mu.Unlock()
   210  
   211  	items := make([]Item, 0, lru.list.Len())
   212  	for e := lru.list.Front(); e != nil; e = e.Next() {
   213  		v := e.Value.(*entry)
   214  		items = append(items, Item{Key: v.key, Value: v.value})
   215  	}
   216  	return items
   217  }
   218  
   219  func (lru *LRUCache) updateInplace(element *list.Element, value any) {
   220  	valueSize := lru.cost(value)
   221  	sizeDiff := valueSize - element.Value.(*entry).size
   222  	element.Value.(*entry).value = value
   223  	element.Value.(*entry).size = valueSize
   224  	lru.size += sizeDiff
   225  	lru.moveToFront(element)
   226  	lru.checkCapacity()
   227  }
   228  
   229  func (lru *LRUCache) moveToFront(element *list.Element) {
   230  	lru.list.MoveToFront(element)
   231  	element.Value.(*entry).timeAccessed = time.Now()
   232  }
   233  
   234  func (lru *LRUCache) addNew(key string, value any) {
   235  	newEntry := &entry{key, value, lru.cost(value), time.Now()}
   236  	element := lru.list.PushFront(newEntry)
   237  	lru.table[key] = element
   238  	lru.size += newEntry.size
   239  	lru.checkCapacity()
   240  }
   241  
   242  func (lru *LRUCache) checkCapacity() {
   243  	// Partially duplicated from Delete
   244  	for lru.size > lru.capacity {
   245  		delElem := lru.list.Back()
   246  		delValue := delElem.Value.(*entry)
   247  		lru.list.Remove(delElem)
   248  		delete(lru.table, delValue.key)
   249  		lru.size -= delValue.size
   250  		lru.evictions++
   251  	}
   252  }