arc.go 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. package lru
  2. import (
  3. "sync"
  4. "github.com/hashicorp/golang-lru/simplelru"
  5. )
  6. // ARCCache is a thread-safe fixed size Adaptive Replacement Cache (ARC).
  7. // ARC is an enhancement over the standard LRU cache in that tracks both
  8. // frequency and recency of use. This avoids a burst in access to new
  9. // entries from evicting the frequently used older entries. It adds some
  10. // additional tracking overhead to a standard LRU cache, computationally
  11. // it is roughly 2x the cost, and the extra memory overhead is linear
  12. // with the size of the cache. ARC has been patented by IBM, but is
  13. // similar to the TwoQueueCache (2Q) which requires setting parameters.
  14. type ARCCache struct {
  15. size int // Size is the total capacity of the cache
  16. p int // P is the dynamic preference towards T1 or T2
  17. t1 *simplelru.LRU // T1 is the LRU for recently accessed items
  18. b1 *simplelru.LRU // B1 is the LRU for evictions from t1
  19. t2 *simplelru.LRU // T2 is the LRU for frequently accessed items
  20. b2 *simplelru.LRU // B2 is the LRU for evictions from t2
  21. lock sync.RWMutex
  22. }
  23. // NewARC creates an ARC of the given size
  24. func NewARC(size int) (*ARCCache, error) {
  25. // Create the sub LRUs
  26. b1, err := simplelru.NewLRU(size, nil)
  27. if err != nil {
  28. return nil, err
  29. }
  30. b2, err := simplelru.NewLRU(size, nil)
  31. if err != nil {
  32. return nil, err
  33. }
  34. t1, err := simplelru.NewLRU(size, nil)
  35. if err != nil {
  36. return nil, err
  37. }
  38. t2, err := simplelru.NewLRU(size, nil)
  39. if err != nil {
  40. return nil, err
  41. }
  42. // Initialize the ARC
  43. c := &ARCCache{
  44. size: size,
  45. p: 0,
  46. t1: t1,
  47. b1: b1,
  48. t2: t2,
  49. b2: b2,
  50. }
  51. return c, nil
  52. }
  53. // Get looks up a key's value from the cache.
  54. func (c *ARCCache) Get(key interface{}) (interface{}, bool) {
  55. c.lock.Lock()
  56. defer c.lock.Unlock()
  57. // Ff the value is contained in T1 (recent), then
  58. // promote it to T2 (frequent)
  59. if val, ok := c.t1.Peek(key); ok {
  60. c.t1.Remove(key)
  61. c.t2.Add(key, val)
  62. return val, ok
  63. }
  64. // Check if the value is contained in T2 (frequent)
  65. if val, ok := c.t2.Get(key); ok {
  66. return val, ok
  67. }
  68. // No hit
  69. return nil, false
  70. }
  71. // Add adds a value to the cache.
  72. func (c *ARCCache) Add(key, value interface{}) {
  73. c.lock.Lock()
  74. defer c.lock.Unlock()
  75. // Check if the value is contained in T1 (recent), and potentially
  76. // promote it to frequent T2
  77. if c.t1.Contains(key) {
  78. c.t1.Remove(key)
  79. c.t2.Add(key, value)
  80. return
  81. }
  82. // Check if the value is already in T2 (frequent) and update it
  83. if c.t2.Contains(key) {
  84. c.t2.Add(key, value)
  85. return
  86. }
  87. // Check if this value was recently evicted as part of the
  88. // recently used list
  89. if c.b1.Contains(key) {
  90. // T1 set is too small, increase P appropriately
  91. delta := 1
  92. b1Len := c.b1.Len()
  93. b2Len := c.b2.Len()
  94. if b2Len > b1Len {
  95. delta = b2Len / b1Len
  96. }
  97. if c.p+delta >= c.size {
  98. c.p = c.size
  99. } else {
  100. c.p += delta
  101. }
  102. // Potentially need to make room in the cache
  103. if c.t1.Len()+c.t2.Len() >= c.size {
  104. c.replace(false)
  105. }
  106. // Remove from B1
  107. c.b1.Remove(key)
  108. // Add the key to the frequently used list
  109. c.t2.Add(key, value)
  110. return
  111. }
  112. // Check if this value was recently evicted as part of the
  113. // frequently used list
  114. if c.b2.Contains(key) {
  115. // T2 set is too small, decrease P appropriately
  116. delta := 1
  117. b1Len := c.b1.Len()
  118. b2Len := c.b2.Len()
  119. if b1Len > b2Len {
  120. delta = b1Len / b2Len
  121. }
  122. if delta >= c.p {
  123. c.p = 0
  124. } else {
  125. c.p -= delta
  126. }
  127. // Potentially need to make room in the cache
  128. if c.t1.Len()+c.t2.Len() >= c.size {
  129. c.replace(true)
  130. }
  131. // Remove from B2
  132. c.b2.Remove(key)
  133. // Add the key to the frequntly used list
  134. c.t2.Add(key, value)
  135. return
  136. }
  137. // Potentially need to make room in the cache
  138. if c.t1.Len()+c.t2.Len() >= c.size {
  139. c.replace(false)
  140. }
  141. // Keep the size of the ghost buffers trim
  142. if c.b1.Len() > c.size-c.p {
  143. c.b1.RemoveOldest()
  144. }
  145. if c.b2.Len() > c.p {
  146. c.b2.RemoveOldest()
  147. }
  148. // Add to the recently seen list
  149. c.t1.Add(key, value)
  150. return
  151. }
  152. // replace is used to adaptively evict from either T1 or T2
  153. // based on the current learned value of P
  154. func (c *ARCCache) replace(b2ContainsKey bool) {
  155. t1Len := c.t1.Len()
  156. if t1Len > 0 && (t1Len > c.p || (t1Len == c.p && b2ContainsKey)) {
  157. k, _, ok := c.t1.RemoveOldest()
  158. if ok {
  159. c.b1.Add(k, nil)
  160. }
  161. } else {
  162. k, _, ok := c.t2.RemoveOldest()
  163. if ok {
  164. c.b2.Add(k, nil)
  165. }
  166. }
  167. }
  168. // Len returns the number of cached entries
  169. func (c *ARCCache) Len() int {
  170. c.lock.RLock()
  171. defer c.lock.RUnlock()
  172. return c.t1.Len() + c.t2.Len()
  173. }
  174. // Keys returns all the cached keys
  175. func (c *ARCCache) Keys() []interface{} {
  176. c.lock.RLock()
  177. defer c.lock.RUnlock()
  178. k1 := c.t1.Keys()
  179. k2 := c.t2.Keys()
  180. return append(k1, k2...)
  181. }
  182. // Remove is used to purge a key from the cache
  183. func (c *ARCCache) Remove(key interface{}) {
  184. c.lock.Lock()
  185. defer c.lock.Unlock()
  186. if c.t1.Remove(key) {
  187. return
  188. }
  189. if c.t2.Remove(key) {
  190. return
  191. }
  192. if c.b1.Remove(key) {
  193. return
  194. }
  195. if c.b2.Remove(key) {
  196. return
  197. }
  198. }
  199. // Purge is used to clear the cache
  200. func (c *ARCCache) Purge() {
  201. c.lock.Lock()
  202. defer c.lock.Unlock()
  203. c.t1.Purge()
  204. c.t2.Purge()
  205. c.b1.Purge()
  206. c.b2.Purge()
  207. }
  208. // Contains is used to check if the cache contains a key
  209. // without updating recency or frequency.
  210. func (c *ARCCache) Contains(key interface{}) bool {
  211. c.lock.RLock()
  212. defer c.lock.RUnlock()
  213. return c.t1.Contains(key) || c.t2.Contains(key)
  214. }
  215. // Peek is used to inspect the cache value of a key
  216. // without updating recency or frequency.
  217. func (c *ARCCache) Peek(key interface{}) (interface{}, bool) {
  218. c.lock.RLock()
  219. defer c.lock.RUnlock()
  220. if val, ok := c.t1.Peek(key); ok {
  221. return val, ok
  222. }
  223. return c.t2.Peek(key)
  224. }