Просмотр исходного кода

Updates to string banks for tests and benchmarks. LRU implementation complexity reduced further.

Matt Bolt 1 месяц назад
Родитель
Сommit
6c66260a80

+ 37 - 26
core/pkg/util/stringutil/lrubank.go

@@ -8,12 +8,12 @@ import (
 
 type lruEntry struct {
 	value string
-	used  time.Time
+	used  int64
 }
 type maxHeap []*lruEntry
 
 func (h maxHeap) Len() int           { return len(h) }
-func (h maxHeap) Less(i, j int) bool { return h[i].used.After(h[j].used) } // newer = "larger"
+func (h maxHeap) Less(i, j int) bool { return h[i].used > h[j].used } // newer = "larger"
 func (h maxHeap) Swap(i, j int)      { h[i], h[j] = h[j], h[i] }
 
 func (h *maxHeap) Push(x any) {
@@ -42,7 +42,7 @@ func nOldest(arr []*lruEntry, n int) []*lruEntry {
 
 	for _, entry := range arr[n:] {
 		// swap in oldest, re-heapify
-		if entry.used.Before(h[0].used) {
+		if entry.used < h[0].used {
 			h[0] = entry
 			heap.Fix(&h, 0)
 		}
@@ -52,15 +52,17 @@ func nOldest(arr []*lruEntry, n int) []*lruEntry {
 }
 
 type lruStringBank struct {
-	lock sync.Mutex
-	stop chan struct{}
-	m    map[string]*lruEntry
+	lock     sync.Mutex
+	stop     chan struct{}
+	m        map[string]*lruEntry
+	capacity int
 }
 
 func NewLruStringBank(capacity int, evictionInterval time.Duration) StringBank {
 	stop := make(chan struct{})
 	bank := &lruStringBank{
-		m: make(map[string]*lruEntry),
+		m:        make(map[string]*lruEntry),
+		capacity: capacity,
 	}
 
 	go func() {
@@ -73,21 +75,7 @@ func NewLruStringBank(capacity int, evictionInterval time.Duration) StringBank {
 
 			// need to take the lock during eviction
 			bank.lock.Lock()
-			if len(bank.m) <= capacity {
-				bank.lock.Unlock()
-				continue
-			}
-
-			// we collect a list of all lru entries so we can max heap the first n elements
-			arr := make([]*lruEntry, 0, len(bank.m))
-			for _, v := range bank.m {
-				arr = append(arr, v)
-			}
-
-			oldest := nOldest(arr, len(bank.m)-capacity)
-			for _, old := range oldest {
-				delete(bank.m, old.value)
-			}
+			evict(bank, capacity)
 			bank.lock.Unlock()
 		}
 	}()
@@ -95,6 +83,23 @@ func NewLruStringBank(capacity int, evictionInterval time.Duration) StringBank {
 	return bank
 }
 
+func evict(bank *lruStringBank, capacity int) {
+	if len(bank.m) <= capacity {
+		return
+	}
+
+	// we collect a list of all lru entries so we can max heap the first n elements
+	arr := make([]*lruEntry, 0, len(bank.m))
+	for _, v := range bank.m {
+		arr = append(arr, v)
+	}
+
+	oldest := nOldest(arr, len(bank.m)-capacity)
+	for _, old := range oldest {
+		delete(bank.m, old.value)
+	}
+}
+
 func (sb *lruStringBank) Stop() {
 	sb.lock.Lock()
 	defer sb.lock.Unlock()
@@ -109,14 +114,17 @@ func (sb *lruStringBank) LoadOrStore(key, value string) (string, bool) {
 	sb.lock.Lock()
 
 	if v, ok := sb.m[key]; ok {
-		v.used = time.Now().UTC()
+		v.used = time.Now().UnixMilli()
 		sb.lock.Unlock()
 		return v.value, ok
 	}
 
 	sb.m[key] = &lruEntry{
 		value: value,
-		used:  time.Now().UTC(),
+		used:  time.Now().UnixMilli(),
+	}
+	if len(sb.m) > (sb.capacity + (sb.capacity / 2)) {
+		evict(sb, sb.capacity)
 	}
 	sb.lock.Unlock()
 	return value, false
@@ -126,7 +134,7 @@ func (sb *lruStringBank) LoadOrStoreFunc(key string, f func() string) (string, b
 	sb.lock.Lock()
 
 	if v, ok := sb.m[key]; ok {
-		v.used = time.Now().UTC()
+		v.used = time.Now().UnixMilli()
 		sb.lock.Unlock()
 		return v.value, ok
 	}
@@ -135,7 +143,10 @@ func (sb *lruStringBank) LoadOrStoreFunc(key string, f func() string) (string, b
 	value := f()
 	sb.m[value] = &lruEntry{
 		value: value,
-		used:  time.Now().UTC(),
+		used:  time.Now().UnixMilli(),
+	}
+	if len(sb.m) > (sb.capacity + (sb.capacity / 2)) {
+		evict(sb, sb.capacity)
 	}
 	sb.lock.Unlock()
 	return value, false

+ 10 - 10
core/pkg/util/stringutil/lrubank_test.go

@@ -234,11 +234,11 @@ func TestClear_PreviousKeysGone(t *testing.T) {
 func TestNOldest_ReturnsCorrectCount(t *testing.T) {
 	now := time.Now()
 	entries := []*lruEntry{
-		{value: "a", used: now.Add(-4 * time.Second)},
-		{value: "b", used: now.Add(-3 * time.Second)},
-		{value: "c", used: now.Add(-2 * time.Second)},
-		{value: "d", used: now.Add(-1 * time.Second)},
-		{value: "e", used: now},
+		{value: "a", used: now.Add(-4 * time.Second).UnixMilli()},
+		{value: "b", used: now.Add(-3 * time.Second).UnixMilli()},
+		{value: "c", used: now.Add(-2 * time.Second).UnixMilli()},
+		{value: "d", used: now.Add(-1 * time.Second).UnixMilli()},
+		{value: "e", used: now.UnixMilli()},
 	}
 
 	oldest := nOldest(entries, 2)
@@ -260,8 +260,8 @@ func TestNOldest_ReturnsCorrectCount(t *testing.T) {
 func TestNOldest_NGreaterThanLen(t *testing.T) {
 	now := time.Now()
 	entries := []*lruEntry{
-		{value: "x", used: now},
-		{value: "y", used: now.Add(-time.Second)},
+		{value: "x", used: now.UnixMilli()},
+		{value: "y", used: now.Add(-time.Second).UnixMilli()},
 	}
 
 	result := nOldest(entries, 10)
@@ -273,8 +273,8 @@ func TestNOldest_NGreaterThanLen(t *testing.T) {
 func TestNOldest_NEqualsLen(t *testing.T) {
 	now := time.Now()
 	entries := []*lruEntry{
-		{value: "x", used: now},
-		{value: "y", used: now.Add(-time.Second)},
+		{value: "x", used: now.UnixMilli()},
+		{value: "y", used: now.Add(-time.Second).UnixMilli()},
 	}
 
 	result := nOldest(entries, 2)
@@ -286,7 +286,7 @@ func TestNOldest_NEqualsLen(t *testing.T) {
 func TestNOldest_NIsZero(t *testing.T) {
 	now := time.Now()
 	entries := []*lruEntry{
-		{value: "x", used: now},
+		{value: "x", used: now.UnixMilli()},
 	}
 
 	result := nOldest(entries, 0)

+ 2 - 2
core/pkg/util/stringutil/mapbank.go

@@ -7,7 +7,7 @@ type stringBank struct {
 	m    map[string]string
 }
 
-func newStringBank() *stringBank {
+func NewStringBank() StringBank {
 	return &stringBank{
 		m: make(map[string]string),
 	}
@@ -21,7 +21,7 @@ func (sb *stringBank) LoadOrStore(key, value string) (string, bool) {
 		return v, ok
 	}
 
-	sb.m[key] = value
+	sb.m[value] = value
 	sb.lock.Unlock()
 	return value, false
 }

+ 1 - 1
core/pkg/util/stringutil/noopbank.go

@@ -2,7 +2,7 @@ package stringutil
 
 type noOpStringBank struct{}
 
-func newNoOpStringBank() *noOpStringBank {
+func NewNoOpStringBank() StringBank {
 	return new(noOpStringBank)
 }
 

+ 1 - 1
core/pkg/util/stringutil/stringutil.go

@@ -34,7 +34,7 @@ var (
 
 	// stringBank is an unbounded string cache that is thread-safe. It is especially useful if
 	// storing a large frequency of dynamically allocated duplicate strings.
-	strings StringBank = newStringBank() // sync.Map
+	strings StringBank = NewStringBank()
 )
 
 func init() {