diff --git a/2q.go b/2q.go index 5e00d9a..8c95252 100644 --- a/2q.go +++ b/2q.go @@ -30,8 +30,10 @@ const ( // head. The ARCCache is similar, but does not require setting any // parameters. type TwoQueueCache[K comparable, V any] struct { - size int - recentSize int + size int + recentSize int + recentRatio float64 + ghostRatio float64 recent simplelru.LRUCache[K, V] frequent simplelru.LRUCache[K, V] @@ -80,6 +82,8 @@ func New2QParams[K comparable, V any](size int, recentRatio, ghostRatio float64) c := &TwoQueueCache[K, V]{ size: size, recentSize: recentSize, + recentRatio: recentRatio, + ghostRatio: ghostRatio, recent: recent, frequent: frequent, recentEvict: recentEvict, @@ -171,6 +175,34 @@ func (c *TwoQueueCache[K, V]) Len() int { return c.recent.Len() + c.frequent.Len() } +// Resize changes the cache size. +func (c *TwoQueueCache[K, V]) Resize(size int) (evicted int) { + c.lock.Lock() + defer c.lock.Unlock() + + // Recalculate the sub-sizes + recentSize := int(float64(size) * c.recentRatio) + evictSize := int(float64(size) * c.ghostRatio) + c.size = size + c.recentSize = recentSize + + // ensureSpace + diff := c.recent.Len() + c.frequent.Len() - size + if diff < 0 { + diff = 0 + } + for i := 0; i < diff; i++ { + c.ensureSpace(true) + } + + // Reallocate the LRUs + c.recent.Resize(size) + c.frequent.Resize(size) + c.recentEvict.Resize(evictSize) + + return diff +} + // Keys returns a slice of the keys in the cache. // The frequently used keys are first in the returned slice. func (c *TwoQueueCache[K, V]) Keys() []K { diff --git a/2q_test.go b/2q_test.go index 0b979df..efa6e86 100644 --- a/2q_test.go +++ b/2q_test.go @@ -218,6 +218,75 @@ func Test2Q_Add_RecentEvict(t *testing.T) { } } +func Test2Q_Resize(t *testing.T) { + l, err := New2Q[int, int](100) + if err != nil { + t.Fatalf("err: %v", err) + } + + // Touch all the entries, should be in t1 + for i := 0; i < 100; i++ { + l.Add(i, i) + } + + evicted := l.Resize(50) + if evicted != 50 { + t.Fatalf("bad: %d", evicted) + } + + if n := l.recent.Len(); n != 50 { + t.Fatalf("bad: %d", n) + } + if n := l.frequent.Len(); n != 0 { + t.Fatalf("bad: %d", n) + } + + l, err = New2Q[int, int](100) + if err != nil { + t.Fatalf("err: %v", err) + } + for i := 0; i < 100; i++ { + l.Add(i, i) + } + + for i := 0; i < 50; i++ { + l.Add(i, i) + } + + evicted = l.Resize(50) + if evicted != 50 { + t.Fatalf("bad: %d", evicted) + } + + if n := l.recent.Len(); n != 12 { + t.Fatalf("bad: %d", n) + } + if n := l.frequent.Len(); n != 38 { + t.Fatalf("bad: %d", n) + } + + l, err = New2Q[int, int](100) + if err != nil { + t.Fatalf("err: %v", err) + } + for i := 0; i < 100; i++ { + l.Add(i, i) + l.Add(i, i) + } + + evicted = l.Resize(50) + if evicted != 50 { + t.Fatalf("bad: %d", evicted) + } + + if n := l.recent.Len(); n != 0 { + t.Fatalf("bad: %d", n) + } + if n := l.frequent.Len(); n != 50 { + t.Fatalf("bad: %d", n) + } +} + func Test2Q(t *testing.T) { l, err := New2Q[int, int](128) if err != nil {