lrubank.go 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  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. m: make(map[string]*lruEntry),
  53. capacity: capacity,
  54. }
  55. go func() {
  56. for {
  57. select {
  58. case <-stop:
  59. return
  60. case <-time.After(evictionInterval):
  61. }
  62. // need to take the lock during eviction
  63. bank.lock.Lock()
  64. evict(bank, capacity)
  65. bank.lock.Unlock()
  66. }
  67. }()
  68. return bank
  69. }
  70. func evict(bank *lruStringBank, capacity int) {
  71. if len(bank.m) <= capacity {
  72. return
  73. }
  74. // we collect a list of all lru entries so we can max heap the first n elements
  75. arr := make([]*lruEntry, 0, len(bank.m))
  76. for _, v := range bank.m {
  77. arr = append(arr, v)
  78. }
  79. oldest := nOldest(arr, len(bank.m)-capacity)
  80. for _, old := range oldest {
  81. delete(bank.m, old.value)
  82. }
  83. }
  84. func (sb *lruStringBank) Stop() {
  85. sb.lock.Lock()
  86. defer sb.lock.Unlock()
  87. if sb.stop != nil {
  88. close(sb.stop)
  89. sb.stop = nil
  90. }
  91. }
  92. func (sb *lruStringBank) LoadOrStore(key, value string) (string, bool) {
  93. sb.lock.Lock()
  94. if v, ok := sb.m[key]; ok {
  95. v.used = time.Now().UnixMilli()
  96. sb.lock.Unlock()
  97. return v.value, ok
  98. }
  99. sb.m[key] = &lruEntry{
  100. value: value,
  101. used: time.Now().UnixMilli(),
  102. }
  103. if len(sb.m) > (sb.capacity + (sb.capacity / 2)) {
  104. evict(sb, sb.capacity)
  105. }
  106. sb.lock.Unlock()
  107. return value, false
  108. }
  109. func (sb *lruStringBank) LoadOrStoreFunc(key string, f func() string) (string, bool) {
  110. sb.lock.Lock()
  111. if v, ok := sb.m[key]; ok {
  112. v.used = time.Now().UnixMilli()
  113. sb.lock.Unlock()
  114. return v.value, ok
  115. }
  116. // create the key and value using the func (the key could be deallocated later)
  117. value := f()
  118. sb.m[value] = &lruEntry{
  119. value: value,
  120. used: time.Now().UnixMilli(),
  121. }
  122. if len(sb.m) > (sb.capacity + (sb.capacity / 2)) {
  123. evict(sb, sb.capacity)
  124. }
  125. sb.lock.Unlock()
  126. return value, false
  127. }
  128. func (sb *lruStringBank) Clear() {
  129. sb.lock.Lock()
  130. sb.m = make(map[string]*lruEntry)
  131. sb.lock.Unlock()
  132. }