2q.go 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  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.LRU
  28. frequent *simplelru.LRU
  29. recentEvict *simplelru.LRU
  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. func (c *TwoQueueCache) Get(key interface{}) (interface{}, bool) {
  76. c.lock.Lock()
  77. defer c.lock.Unlock()
  78. // Check if this is a frequent value
  79. if val, ok := c.frequent.Get(key); ok {
  80. return val, ok
  81. }
  82. // If the value is contained in recent, then we
  83. // promote it to frequent
  84. if val, ok := c.recent.Peek(key); ok {
  85. c.recent.Remove(key)
  86. c.frequent.Add(key, val)
  87. return val, ok
  88. }
  89. // No hit
  90. return nil, false
  91. }
  92. func (c *TwoQueueCache) Add(key, value interface{}) {
  93. c.lock.Lock()
  94. defer c.lock.Unlock()
  95. // Check if the value is frequently used already,
  96. // and just update the value
  97. if c.frequent.Contains(key) {
  98. c.frequent.Add(key, value)
  99. return
  100. }
  101. // Check if the value is recently used, and promote
  102. // the value into the frequent list
  103. if c.recent.Contains(key) {
  104. c.recent.Remove(key)
  105. c.frequent.Add(key, value)
  106. return
  107. }
  108. // If the value was recently evicted, add it to the
  109. // frequently used list
  110. if c.recentEvict.Contains(key) {
  111. c.ensureSpace(true)
  112. c.recentEvict.Remove(key)
  113. c.frequent.Add(key, value)
  114. return
  115. }
  116. // Add to the recently seen list
  117. c.ensureSpace(false)
  118. c.recent.Add(key, value)
  119. return
  120. }
  121. // ensureSpace is used to ensure we have space in the cache
  122. func (c *TwoQueueCache) ensureSpace(recentEvict bool) {
  123. // If we have space, nothing to do
  124. recentLen := c.recent.Len()
  125. freqLen := c.frequent.Len()
  126. if recentLen+freqLen < c.size {
  127. return
  128. }
  129. // If the recent buffer is larger than
  130. // the target, evict from there
  131. if recentLen > 0 && (recentLen > c.recentSize || (recentLen == c.recentSize && !recentEvict)) {
  132. k, _, _ := c.recent.RemoveOldest()
  133. c.recentEvict.Add(k, nil)
  134. return
  135. }
  136. // Remove from the frequent list otherwise
  137. c.frequent.RemoveOldest()
  138. }
  139. func (c *TwoQueueCache) Len() int {
  140. c.lock.RLock()
  141. defer c.lock.RUnlock()
  142. return c.recent.Len() + c.frequent.Len()
  143. }
  144. func (c *TwoQueueCache) Keys() []interface{} {
  145. c.lock.RLock()
  146. defer c.lock.RUnlock()
  147. k1 := c.frequent.Keys()
  148. k2 := c.recent.Keys()
  149. return append(k1, k2...)
  150. }
  151. func (c *TwoQueueCache) Remove(key interface{}) {
  152. c.lock.Lock()
  153. defer c.lock.Unlock()
  154. if c.frequent.Remove(key) {
  155. return
  156. }
  157. if c.recent.Remove(key) {
  158. return
  159. }
  160. if c.recentEvict.Remove(key) {
  161. return
  162. }
  163. }
  164. func (c *TwoQueueCache) Purge() {
  165. c.lock.Lock()
  166. defer c.lock.Unlock()
  167. c.recent.Purge()
  168. c.frequent.Purge()
  169. c.recentEvict.Purge()
  170. }
  171. func (c *TwoQueueCache) Contains(key interface{}) bool {
  172. c.lock.RLock()
  173. defer c.lock.RUnlock()
  174. return c.frequent.Contains(key) || c.recent.Contains(key)
  175. }
  176. func (c *TwoQueueCache) Peek(key interface{}) (interface{}, bool) {
  177. c.lock.RLock()
  178. defer c.lock.RUnlock()
  179. if val, ok := c.frequent.Peek(key); ok {
  180. return val, ok
  181. }
  182. return c.recent.Peek(key)
  183. }