chore: cleanup code

This commit is contained in:
Larvan2 2023-12-16 20:38:20 +08:00
parent ac381736a5
commit 147400fbe0
3 changed files with 155 additions and 103 deletions

View File

@ -29,7 +29,6 @@ type ARC[K comparable, V any] struct {
mutex sync.Mutex mutex sync.Mutex
len int len int
cache map[K]*entry[K, V] cache map[K]*entry[K, V]
staleReturn bool
} }
// New returns a new Adaptive Replacement Cache (ARC). // New returns a new Adaptive Replacement Cache (ARC).
@ -74,21 +73,23 @@ func (a *ARC[K, V]) SetWithExpire(key K, value V, expires time.Time) {
func (a *ARC[K, V]) setWithExpire(key K, value V, expires time.Time) { func (a *ARC[K, V]) setWithExpire(key K, value V, expires time.Time) {
ent, ok := a.cache[key] ent, ok := a.cache[key]
if ok != true { if !ok {
a.len++ a.len++
ent := &entry[K, V]{key: key, value: value, ghost: false, expires: expires.Unix()} ent := &entry[K, V]{key: key, value: value, ghost: false, expires: expires.Unix()}
a.req(ent) a.req(ent)
a.cache[key] = ent a.cache[key] = ent
} else { return
}
if ent.ghost { if ent.ghost {
a.len++ a.len++
} }
ent.value = value ent.value = value
ent.ghost = false ent.ghost = false
ent.expires = expires.Unix() ent.expires = expires.Unix()
a.req(ent) a.req(ent)
} }
}
// Get retrieves a previously via Set inserted entry. // Get retrieves a previously via Set inserted entry.
// This optimizes future access to this entry (side effect). // This optimizes future access to this entry (side effect).
@ -97,25 +98,25 @@ func (a *ARC[K, V]) Get(key K) (value V, ok bool) {
defer a.mutex.Unlock() defer a.mutex.Unlock()
ent, ok := a.get(key) ent, ok := a.get(key)
if ok { if !ok {
return ent.value, true
}
return lo.Empty[V](), false return lo.Empty[V](), false
} }
return ent.value, true
}
func (a *ARC[K, V]) get(key K) (e *entry[K, V], ok bool) { func (a *ARC[K, V]) get(key K) (e *entry[K, V], ok bool) {
ent, ok := a.cache[key] ent, ok := a.cache[key]
if ok { if !ok {
return ent, false
}
a.req(ent) a.req(ent)
return ent, !ent.ghost return ent, !ent.ghost
} }
return ent, false
}
// GetWithExpire returns any representation of a cached response, // GetWithExpire returns any representation of a cached response,
// a time.Time Give expected expires, // a time.Time Give expected expires,
// and a bool set to true if the key was found. // and a bool set to true if the key was found.
// This method will NOT check the maxAge of element and will NOT update the expires. // This method will NOT update the expires.
func (a *ARC[K, V]) GetWithExpire(key K) (V, time.Time, bool) { func (a *ARC[K, V]) GetWithExpire(key K) (V, time.Time, bool) {
a.mutex.Lock() a.mutex.Lock()
defer a.mutex.Unlock() defer a.mutex.Unlock()
@ -138,10 +139,11 @@ func (a *ARC[K, V]) Len() int {
} }
func (a *ARC[K, V]) req(ent *entry[K, V]) { func (a *ARC[K, V]) req(ent *entry[K, V]) {
if ent.ll == a.t1 || ent.ll == a.t2 { switch {
case ent.ll == a.t1 || ent.ll == a.t2:
// Case I // Case I
ent.setMRU(a.t2) ent.setMRU(a.t2)
} else if ent.ll == a.b1 { case ent.ll == a.b1:
// Case II // Case II
// Cache Miss in t1 and t2 // Cache Miss in t1 and t2
@ -152,16 +154,11 @@ func (a *ARC[K, V]) req(ent *entry[K, V]) {
} else { } else {
d = a.b2.Len() / a.b1.Len() d = a.b2.Len() / a.b1.Len()
} }
a.p = min(a.p+d, a.c)
// a.p = min(a.p+d, a.c)
a.p = a.p + d
if a.c < a.p {
a.p = a.c
}
a.replace(ent) a.replace(ent)
ent.setMRU(a.t2) ent.setMRU(a.t2)
} else if ent.ll == a.b2 { case ent.ll == a.b2:
// Case III // Case III
// Cache Miss in t1 and t2 // Cache Miss in t1 and t2
@ -172,35 +169,30 @@ func (a *ARC[K, V]) req(ent *entry[K, V]) {
} else { } else {
d = a.b1.Len() / a.b2.Len() d = a.b1.Len() / a.b2.Len()
} }
//a.p = max(a.p-d, 0) a.p = max(a.p-d, 0)
a.p = a.p - d
if a.p < 0 {
a.p = 0
}
a.replace(ent) a.replace(ent)
ent.setMRU(a.t2) ent.setMRU(a.t2)
} else if ent.ll == nil { case ent.ll == nil && a.t1.Len()+a.b1.Len() == a.c:
// Case IV // Case IV A
if a.t1.Len()+a.b1.Len() == a.c {
// Case A
if a.t1.Len() < a.c { if a.t1.Len() < a.c {
a.delLRU(a.b1) a.delLRU(a.b1)
a.replace(ent) a.replace(ent)
} else { } else {
a.delLRU(a.t1) a.delLRU(a.t1)
} }
} else if a.t1.Len()+a.b1.Len() < a.c { ent.setMRU(a.t1)
// Case B case ent.ll == nil && a.t1.Len()+a.b1.Len() < a.c:
// Case IV B
if a.t1.Len()+a.t2.Len()+a.b1.Len()+a.b2.Len() >= a.c { if a.t1.Len()+a.t2.Len()+a.b1.Len()+a.b2.Len() >= a.c {
if a.t1.Len()+a.t2.Len()+a.b1.Len()+a.b2.Len() == 2*a.c { if a.t1.Len()+a.t2.Len()+a.b1.Len()+a.b2.Len() == 2*a.c {
a.delLRU(a.b2) a.delLRU(a.b2)
} }
a.replace(ent) a.replace(ent)
} }
} ent.setMRU(a.t1)
case ent.ll == nil:
// Case IV, not A nor B
ent.setMRU(a.t1) ent.setMRU(a.t1)
} }
} }
@ -227,3 +219,17 @@ func (a *ARC[K, V]) replace(ent *entry[K, V]) {
lru.setMRU(a.b2) lru.setMRU(a.b2)
} }
} }
func min(a, b int) int {
if a < b {
return a
}
return b
}
func max(a int, b int) int {
if a < b {
return b
}
return a
}

View File

@ -1,59 +1,105 @@
package arc package arc
import "testing" import (
"testing"
)
func TestBasic(t *testing.T) { func TestInsertion(t *testing.T) {
cache := New[string, string](WithSize[string, string](3)) cache := New[string, string](WithSize[string, string](3))
if cache.Len() != 0 { if got, want := cache.Len(), 0; got != want {
t.Error("Empty cache should have length 0") t.Errorf("empty cache.Len(): got %d want %d", cache.Len(), want)
} }
cache.Set("Hello", "World") const (
if cache.Len() != 1 { k1 = "Hello"
t.Error("Cache should have length 1") k2 = "Hallo"
k3 = "Ciao"
k4 = "Salut"
v1 = "World"
v2 = "Worlds"
v3 = "Welt"
)
// Insert the first value
cache.Set(k1, v1)
if got, want := cache.Len(), 1; got != want {
t.Errorf("insertion of key #%d: cache.Len(): got %d want %d", want, cache.Len(), want)
}
if got, ok := cache.Get(k1); !ok || got != v1 {
t.Errorf("cache.Get(%v): got (%v,%t) want (%v,true)", k1, got, ok, v1)
} }
var val interface{} // Replace existing value for a given key
var ok bool cache.Set(k1, v2)
if got, want := cache.Len(), 1; got != want {
if val, ok = cache.Get("Hello"); val != "World" || ok != true { t.Errorf("re-insertion: cache.Len(): got %d want %d", cache.Len(), want)
t.Error("Didn't set \"Hello\" to \"World\"") }
if got, ok := cache.Get(k1); !ok || got != v2 {
t.Errorf("re-insertion: cache.Get(%v): got (%v,%t) want (%v,true)", k1, got, ok, v2)
} }
cache.Set("Hello", "World1") // Add a second different key
if cache.Len() != 1 { cache.Set(k2, v3)
t.Error("Inserting the same entry multiple times shouldn't increase cache size") if got, want := cache.Len(), 2; got != want {
t.Errorf("insertion of key #%d: cache.Len(): got %d want %d", want, cache.Len(), want)
}
if got, ok := cache.Get(k1); !ok || got != v2 {
t.Errorf("cache.Get(%v): got (%v,%t) want (%v,true)", k1, got, ok, v2)
}
if got, ok := cache.Get(k2); !ok || got != v3 {
t.Errorf("cache.Get(%v): got (%v,%t) want (%v,true)", k2, got, ok, v3)
} }
if val, ok = cache.Get("Hello"); val != "World1" || ok != true { // Fill cache
t.Error("Didn't update \"Hello\" to \"World1\"") cache.Set(k3, v1)
if got, want := cache.Len(), 3; got != want {
t.Errorf("insertion of key #%d: cache.Len(): got %d want %d", want, cache.Len(), want)
} }
cache.Set("Hallo", "Welt") // Exceed size, this should not exceed size:
if cache.Len() != 2 { cache.Set(k4, v1)
t.Error("Inserting two different entries should result into lenght=2") if got, want := cache.Len(), 3; got != want {
} t.Errorf("insertion of key out of size: cache.Len(): got %d want %d", cache.Len(), want)
if val, ok = cache.Get("Hallo"); val != "Welt" || ok != true {
t.Error("Didn't set \"Hallo\" to \"Welt\"")
} }
} }
func TestBasicReplace(t *testing.T) { func TestEviction(t *testing.T) {
cache := New[string, string](WithSize[string, string](3)) size := 3
cache := New[string, string](WithSize[string, string](size))
cache.Set("Hello", "Hallo") if got, want := cache.Len(), 0; got != want {
cache.Set("World", "Welt") t.Errorf("empty cache.Len(): got %d want %d", cache.Len(), want)
cache.Get("World")
cache.Set("Cache", "Cache")
cache.Set("Replace", "Ersetzen")
value, ok := cache.Get("World")
if !ok || value != "Welt" {
t.Error("ARC should have replaced \"Hello\"")
} }
if cache.Len() != 3 { tests := []struct {
t.Error("ARC should have a maximum size of 3") k, v string
}{
{"k1", "v1"},
{"k2", "v2"},
{"k3", "v3"},
{"k4", "v4"},
}
for i, tt := range tests[:size] {
cache.Set(tt.k, tt.v)
if got, want := cache.Len(), i+1; got != want {
t.Errorf("insertion of key #%d: cache.Len(): got %d want %d", want, cache.Len(), want)
}
}
// Exceed size and check we don't outgrow it:
cache.Set(tests[size].k, tests[size].v)
if got := cache.Len(); got != size {
t.Errorf("insertion of overflow key #%d: cache.Len(): got %d want %d", 4, cache.Len(), size)
}
// Check that LRU got evicted:
if got, ok := cache.Get(tests[0].k); ok || got != "" {
t.Errorf("cache.Get(%v): got (%v,%t) want (<nil>,true)", tests[0].k, got, ok)
}
for _, tt := range tests[1:] {
if got, ok := cache.Get(tt.k); !ok || got != tt.v {
t.Errorf("cache.Get(%v): got (%v,%t) want (%v,true)", tt.k, got, ok, tt.v)
}
} }
} }

View File

@ -80,7 +80,7 @@ func New[K comparable, V any](options ...Option[K, V]) *LruCache[K, V] {
return lc return lc
} }
// Get returns the any representation of a cached response and a bool // Get returns any representation of a cached response and a bool
// set to true if the key was found. // set to true if the key was found.
func (c *LruCache[K, V]) Get(key K) (V, bool) { func (c *LruCache[K, V]) Get(key K) (V, bool) {
c.mu.Lock() c.mu.Lock()
@ -110,7 +110,7 @@ func (c *LruCache[K, V]) GetOrStore(key K, constructor func() V) (V, bool) {
return value, true return value, true
} }
// GetWithExpire returns the any representation of a cached response, // GetWithExpire returns any representation of a cached response,
// a time.Time Give expected expires, // a time.Time Give expected expires,
// and a bool set to true if the key was found. // and a bool set to true if the key was found.
// This method will NOT check the maxAge of element and will NOT update the expires. // This method will NOT check the maxAge of element and will NOT update the expires.
@ -135,7 +135,7 @@ func (c *LruCache[K, V]) Exist(key K) bool {
return ok return ok
} }
// Set stores the any representation of a response for a given key. // Set stores any representation of a response for a given key.
func (c *LruCache[K, V]) Set(key K, value V) { func (c *LruCache[K, V]) Set(key K, value V) {
c.mu.Lock() c.mu.Lock()
defer c.mu.Unlock() defer c.mu.Unlock()
@ -151,7 +151,7 @@ func (c *LruCache[K, V]) set(key K, value V) {
c.setWithExpire(key, value, time.Unix(expires, 0)) c.setWithExpire(key, value, time.Unix(expires, 0))
} }
// SetWithExpire stores the any representation of a response for a given key and given expires. // SetWithExpire stores any representation of a response for a given key and given expires.
// The expires time will round to second. // The expires time will round to second.
func (c *LruCache[K, V]) SetWithExpire(key K, value V, expires time.Time) { func (c *LruCache[K, V]) SetWithExpire(key K, value V, expires time.Time) {
c.mu.Lock() c.mu.Lock()