Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: hashicorp/golang-lru
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v2.0.6
Choose a base ref
...
head repository: hashicorp/golang-lru
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v2.0.7
Choose a head ref
  • 3 commits
  • 6 files changed
  • 3 contributors

Commits on Aug 24, 2023

  1. Copy the full SHA
    56a2dc0 View commit details

Commits on Sep 14, 2023

  1. expirable LRU: fix so that Get/Peek cannot return an ok and empty val…

    …ue (#156)
    
    * expirable LRU: fix so that Get/Peek cannot return an ok and empty value
    irenarindos authored Sep 14, 2023
    Copy the full SHA
    d46c1d9 View commit details

Commits on Sep 21, 2023

  1. add a Resize method to 2Q

    veloting authored and mgaffney committed Sep 21, 2023

    Verified

    This commit was signed with the committer’s verified signature.
    mgaffney Michael Gaffney
    Copy the full SHA
    d851586 View commit details
Showing with 119 additions and 8 deletions.
  1. +34 −2 2q.go
  2. +69 −0 2q_test.go
  3. +1 −1 arc/go.mod
  4. +2 −2 arc/go.sum
  5. +2 −2 expirable/expirable_lru.go
  6. +11 −1 expirable/expirable_lru_test.go
36 changes: 34 additions & 2 deletions 2q.go
Original file line number Diff line number Diff line change
@@ -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 {
69 changes: 69 additions & 0 deletions 2q_test.go
Original file line number Diff line number Diff line change
@@ -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 {
2 changes: 1 addition & 1 deletion arc/go.mod
Original file line number Diff line number Diff line change
@@ -2,4 +2,4 @@ module github.com/hashicorp/golang-lru/arc/v2

go 1.18

require github.com/hashicorp/golang-lru/v2 v2.0.5
require github.com/hashicorp/golang-lru/v2 v2.0.6
4 changes: 2 additions & 2 deletions arc/go.sum
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
github.com/hashicorp/golang-lru/v2 v2.0.5 h1:wW7h1TG88eUIJ2i69gaE3uNVtEPIagzhGvHgwfx2Vm4=
github.com/hashicorp/golang-lru/v2 v2.0.5/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/hashicorp/golang-lru/v2 v2.0.6 h1:3xi/Cafd1NaoEnS/yDssIiuVeDVywU0QdFGl3aQaQHM=
github.com/hashicorp/golang-lru/v2 v2.0.6/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
4 changes: 2 additions & 2 deletions expirable/expirable_lru.go
Original file line number Diff line number Diff line change
@@ -153,7 +153,7 @@ func (c *LRU[K, V]) Get(key K) (value V, ok bool) {
if ent, ok = c.items[key]; ok {
// Expired item check
if time.Now().After(ent.ExpiresAt) {
return
return value, false
}
c.evictList.MoveToFront(ent)
return ent.Value, true
@@ -179,7 +179,7 @@ func (c *LRU[K, V]) Peek(key K) (value V, ok bool) {
if ent, ok = c.items[key]; ok {
// Expired item check
if time.Now().After(ent.ExpiresAt) {
return
return value, false
}
return ent.Value, true
}
12 changes: 11 additions & 1 deletion expirable/expirable_lru_test.go
Original file line number Diff line number Diff line change
@@ -390,7 +390,17 @@ func TestLoadingExpired(t *testing.T) {
t.Fatalf("should be true")
}

time.Sleep(time.Millisecond * 100) // wait for entry to expire
for {
result, ok := lc.Get("key1")
if ok && result == "" {
t.Fatalf("ok should return a result")
}
if !ok {
break
}
}

time.Sleep(time.Millisecond * 100) // wait for expiration reaper
if lc.Len() != 0 {
t.Fatalf("length differs from expected")
}