lrubank.go 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. package stringutil
  2. import (
  3. "container/heap"
  4. "sync"
  5. "time"
  6. )
  7. type lruEntry struct {
  8. value string
  9. used int64
  10. }
  11. type maxHeap []*lruEntry
  12. func (h maxHeap) Len() int { return len(h) }
  13. func (h maxHeap) Less(i, j int) bool { return h[i].used > h[j].used } // newer = "larger"
  14. func (h maxHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
  15. func (h *maxHeap) Push(x any) {
  16. *h = append(*h, x.(*lruEntry))
  17. }
  18. func (h *maxHeap) Pop() any {
  19. old := *h
  20. n := len(old)
  21. x := old[n-1]
  22. *h = old[:n-1]
  23. return x
  24. }
  25. func nOldest(arr []*lruEntry, n int) []*lruEntry {
  26. if n <= 0 {
  27. return []*lruEntry{}
  28. }
  29. if n >= len(arr) {
  30. return arr
  31. }
  32. h := maxHeap(arr[:n])
  33. heap.Init(&h)
  34. for _, entry := range arr[n:] {
  35. // swap in oldest, re-heapify
  36. if entry.used < h[0].used {
  37. h[0] = entry
  38. heap.Fix(&h, 0)
  39. }
  40. }
  41. return []*lruEntry(h)
  42. }
  43. type lruStringBank struct {
  44. lock sync.Mutex
  45. stop chan struct{}
  46. m map[string]*lruEntry
  47. capacity int
  48. }
  49. func NewLruStringBank(capacity int, evictionInterval time.Duration) StringBank {
  50. stop := make(chan struct{})
  51. bank := &lruStringBank{
  52. stop: stop,
  53. m: make(map[string]*lruEntry),
  54. capacity: capacity,
  55. }
  56. go func() {
  57. for {
  58. select {
  59. case <-stop:
  60. return
  61. case <-time.After(evictionInterval):
  62. }
  63. // need to take the lock during eviction
  64. bank.lock.Lock()
  65. evict(bank, capacity)
  66. bank.lock.Unlock()
  67. }
  68. }()
  69. return bank
  70. }
  71. func evict(bank *lruStringBank, capacity int) {
  72. if len(bank.m) <= capacity {
  73. return
  74. }
  75. // we collect a list of all lru entries so we can max heap the first n elements
  76. arr := make([]*lruEntry, 0, len(bank.m))
  77. for _, v := range bank.m {
  78. arr = append(arr, v)
  79. }
  80. oldest := nOldest(arr, len(bank.m)-capacity)
  81. for _, old := range oldest {
  82. delete(bank.m, old.value)
  83. }
  84. }
  85. func (sb *lruStringBank) Stop() {
  86. sb.lock.Lock()
  87. defer sb.lock.Unlock()
  88. if sb.stop != nil {
  89. close(sb.stop)
  90. sb.stop = nil
  91. }
  92. }
  93. func (sb *lruStringBank) LoadOrStore(key, value string) (string, bool) {
  94. sb.lock.Lock()
  95. if v, ok := sb.m[key]; ok {
  96. v.used = time.Now().UnixMilli()
  97. sb.lock.Unlock()
  98. return v.value, ok
  99. }
  100. sb.m[key] = &lruEntry{
  101. value: value,
  102. used: time.Now().UnixMilli(),
  103. }
  104. if len(sb.m) > (sb.capacity + (sb.capacity / 2)) {
  105. evict(sb, sb.capacity)
  106. }
  107. sb.lock.Unlock()
  108. return value, false
  109. }
  110. func (sb *lruStringBank) LoadOrStoreFunc(key string, f func() string) (string, bool) {
  111. sb.lock.Lock()
  112. if v, ok := sb.m[key]; ok {
  113. v.used = time.Now().UnixMilli()
  114. sb.lock.Unlock()
  115. return v.value, ok
  116. }
  117. // create the key and value using the func (the key could be deallocated later)
  118. value := f()
  119. sb.m[value] = &lruEntry{
  120. value: value,
  121. used: time.Now().UnixMilli(),
  122. }
  123. if len(sb.m) > (sb.capacity + (sb.capacity / 2)) {
  124. evict(sb, sb.capacity)
  125. }
  126. sb.lock.Unlock()
  127. return value, false
  128. }
  129. func (sb *lruStringBank) Clear() {
  130. sb.lock.Lock()
  131. sb.m = make(map[string]*lruEntry)
  132. sb.lock.Unlock()
  133. }