You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

224 lines
5.5 KiB

  1. package lru
  2. import (
  3. "fmt"
  4. "sync"
  5. "github.com/hashicorp/golang-lru/simplelru"
  6. )
  7. const (
  8. // Default2QRecentRatio is the ratio of the 2Q cache dedicated
  9. // to recently added entries that have only been accessed once.
  10. Default2QRecentRatio = 0.25
  11. // Default2QGhostEntries is the default ratio of ghost
  12. // entries kept to track entries recently evicted
  13. Default2QGhostEntries = 0.50
  14. )
  15. // TwoQueueCache is a thread-safe fixed size 2Q cache.
  16. // 2Q is an enhancement over the standard LRU cache
  17. // in that it tracks both frequently and recently used
  18. // entries separately. This avoids a burst in access to new
  19. // entries from evicting frequently used entries. It adds some
  20. // additional tracking overhead to the standard LRU cache, and is
  21. // computationally about 2x the cost, and adds some metadata over
  22. // head. The ARCCache is similar, but does not require setting any
  23. // parameters.
  24. type TwoQueueCache struct {
  25. size int
  26. recentSize int
  27. recent simplelru.LRUCache
  28. frequent simplelru.LRUCache
  29. recentEvict simplelru.LRUCache
  30. lock sync.RWMutex
  31. }
  32. // New2Q creates a new TwoQueueCache using the default
  33. // values for the parameters.
  34. func New2Q(size int) (*TwoQueueCache, error) {
  35. return New2QParams(size, Default2QRecentRatio, Default2QGhostEntries)
  36. }
  37. // New2QParams creates a new TwoQueueCache using the provided
  38. // parameter values.
  39. func New2QParams(size int, recentRatio float64, ghostRatio float64) (*TwoQueueCache, error) {
  40. if size <= 0 {
  41. return nil, fmt.Errorf("invalid size")
  42. }
  43. if recentRatio < 0.0 || recentRatio > 1.0 {
  44. return nil, fmt.Errorf("invalid recent ratio")
  45. }
  46. if ghostRatio < 0.0 || ghostRatio > 1.0 {
  47. return nil, fmt.Errorf("invalid ghost ratio")
  48. }
  49. // Determine the sub-sizes
  50. recentSize := int(float64(size) * recentRatio)
  51. evictSize := int(float64(size) * ghostRatio)
  52. // Allocate the LRUs
  53. recent, err := simplelru.NewLRU(size, nil)
  54. if err != nil {
  55. return nil, err
  56. }
  57. frequent, err := simplelru.NewLRU(size, nil)
  58. if err != nil {
  59. return nil, err
  60. }
  61. recentEvict, err := simplelru.NewLRU(evictSize, nil)
  62. if err != nil {
  63. return nil, err
  64. }
  65. // Initialize the cache
  66. c := &TwoQueueCache{
  67. size: size,
  68. recentSize: recentSize,
  69. recent: recent,
  70. frequent: frequent,
  71. recentEvict: recentEvict,
  72. }
  73. return c, nil
  74. }
  75. // Get looks up a key's value from the cache.
  76. func (c *TwoQueueCache) Get(key interface{}) (value interface{}, ok bool) {
  77. c.lock.Lock()
  78. defer c.lock.Unlock()
  79. // Check if this is a frequent value
  80. if val, ok := c.frequent.Get(key); ok {
  81. return val, ok
  82. }
  83. // If the value is contained in recent, then we
  84. // promote it to frequent
  85. if val, ok := c.recent.Peek(key); ok {
  86. c.recent.Remove(key)
  87. c.frequent.Add(key, val)
  88. return val, ok
  89. }
  90. // No hit
  91. return nil, false
  92. }
  93. // Add adds a value to the cache.
  94. func (c *TwoQueueCache) Add(key, value interface{}) {
  95. c.lock.Lock()
  96. defer c.lock.Unlock()
  97. // Check if the value is frequently used already,
  98. // and just update the value
  99. if c.frequent.Contains(key) {
  100. c.frequent.Add(key, value)
  101. return
  102. }
  103. // Check if the value is recently used, and promote
  104. // the value into the frequent list
  105. if c.recent.Contains(key) {
  106. c.recent.Remove(key)
  107. c.frequent.Add(key, value)
  108. return
  109. }
  110. // If the value was recently evicted, add it to the
  111. // frequently used list
  112. if c.recentEvict.Contains(key) {
  113. c.ensureSpace(true)
  114. c.recentEvict.Remove(key)
  115. c.frequent.Add(key, value)
  116. return
  117. }
  118. // Add to the recently seen list
  119. c.ensureSpace(false)
  120. c.recent.Add(key, value)
  121. return
  122. }
  123. // ensureSpace is used to ensure we have space in the cache
  124. func (c *TwoQueueCache) ensureSpace(recentEvict bool) {
  125. // If we have space, nothing to do
  126. recentLen := c.recent.Len()
  127. freqLen := c.frequent.Len()
  128. if recentLen+freqLen < c.size {
  129. return
  130. }
  131. // If the recent buffer is larger than
  132. // the target, evict from there
  133. if recentLen > 0 && (recentLen > c.recentSize || (recentLen == c.recentSize && !recentEvict)) {
  134. k, _, _ := c.recent.RemoveOldest()
  135. c.recentEvict.Add(k, nil)
  136. return
  137. }
  138. // Remove from the frequent list otherwise
  139. c.frequent.RemoveOldest()
  140. }
  141. // Len returns the number of items in the cache.
  142. func (c *TwoQueueCache) Len() int {
  143. c.lock.RLock()
  144. defer c.lock.RUnlock()
  145. return c.recent.Len() + c.frequent.Len()
  146. }
  147. // Keys returns a slice of the keys in the cache.
  148. // The frequently used keys are first in the returned slice.
  149. func (c *TwoQueueCache) Keys() []interface{} {
  150. c.lock.RLock()
  151. defer c.lock.RUnlock()
  152. k1 := c.frequent.Keys()
  153. k2 := c.recent.Keys()
  154. return append(k1, k2...)
  155. }
  156. // Remove removes the provided key from the cache.
  157. func (c *TwoQueueCache) Remove(key interface{}) {
  158. c.lock.Lock()
  159. defer c.lock.Unlock()
  160. if c.frequent.Remove(key) {
  161. return
  162. }
  163. if c.recent.Remove(key) {
  164. return
  165. }
  166. if c.recentEvict.Remove(key) {
  167. return
  168. }
  169. }
  170. // Purge is used to completely clear the cache.
  171. func (c *TwoQueueCache) Purge() {
  172. c.lock.Lock()
  173. defer c.lock.Unlock()
  174. c.recent.Purge()
  175. c.frequent.Purge()
  176. c.recentEvict.Purge()
  177. }
  178. // Contains is used to check if the cache contains a key
  179. // without updating recency or frequency.
  180. func (c *TwoQueueCache) Contains(key interface{}) bool {
  181. c.lock.RLock()
  182. defer c.lock.RUnlock()
  183. return c.frequent.Contains(key) || c.recent.Contains(key)
  184. }
  185. // Peek is used to inspect the cache value of a key
  186. // without updating recency or frequency.
  187. func (c *TwoQueueCache) Peek(key interface{}) (value interface{}, ok bool) {
  188. c.lock.RLock()
  189. defer c.lock.RUnlock()
  190. if val, ok := c.frequent.Peek(key); ok {
  191. return val, ok
  192. }
  193. return c.recent.Peek(key)
  194. }