diff --git a/arc.go b/arc.go index 53e2040..a47d665 100644 --- a/arc.go +++ b/arc.go @@ -13,7 +13,7 @@ type ARC struct { b1 *list.List t2 *list.List b2 *list.List - mutex sync.RWMutex + mutex sync.Mutex len int cache map[interface{}]*entry } @@ -34,12 +34,12 @@ func New(c int) *ARC { // Put inserts a new key-value pair into the cache. // This optimizes future access to this entry (side effect). -func (a *ARC) Put(key, value interface{}) bool { +func (a *ARC) Put(key, value interface{}) { a.mutex.Lock() defer a.mutex.Unlock() ent, ok := a.cache[key] - if ok != true { + if !ok { a.len++ ent = &entry{ @@ -50,15 +50,14 @@ func (a *ARC) Put(key, value interface{}) bool { a.req(ent) a.cache[key] = ent - } else { - if ent.ghost { - a.len++ - } - ent.value = value - ent.ghost = false - a.req(ent) + return + } + if ent.ghost { + a.len++ } - return ok + ent.value = value + ent.ghost = false + a.req(ent) } // Get retrieves a previously via Set inserted entry. @@ -68,11 +67,11 @@ func (a *ARC) Get(key interface{}) (value interface{}, ok bool) { defer a.mutex.Unlock() ent, ok := a.cache[key] - if ok { - a.req(ent) - return ent.value, !ent.ghost + if !ok { + return nil, false } - return nil, false + a.req(ent) + return ent.value, !ent.ghost } // Len determines the number of currently cached entries. @@ -85,10 +84,11 @@ func (a *ARC) Len() int { } func (a *ARC) req(ent *entry) { - if ent.ll == a.t1 || ent.ll == a.t2 { + switch { + case ent.ll == a.t1 || ent.ll == a.t2: // Case I ent.setMRU(a.t2) - } else if ent.ll == a.b1 { + case ent.ll == a.b1: // Case II // Cache Miss in t1 and t2 @@ -103,7 +103,7 @@ func (a *ARC) req(ent *entry) { a.replace(ent) ent.setMRU(a.t2) - } else if ent.ll == a.b2 { + case ent.ll == a.b2: // Case III // Cache Miss in t1 and t2 @@ -118,27 +118,26 @@ func (a *ARC) req(ent *entry) { a.replace(ent) ent.setMRU(a.t2) - } else if ent.ll == nil { - // Case IV - - if a.t1.Len()+a.b1.Len() == a.c { - // Case A - if a.t1.Len() < a.c { - a.delLRU(a.b1) - a.replace(ent) - } else { - a.delLRU(a.t1) - } - } else if a.t1.Len()+a.b1.Len() < a.c { - // Case 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() == 2*a.c { - a.delLRU(a.b2) - } - a.replace(ent) + case ent.ll == nil && a.t1.Len()+a.b1.Len() == a.c: + // Case IV A + if a.t1.Len() < a.c { + a.delLRU(a.b1) + a.replace(ent) + } else { + a.delLRU(a.t1) + } + ent.setMRU(a.t1) + 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() == 2*a.c { + a.delLRU(a.b2) } + a.replace(ent) } - + ent.setMRU(a.t1) + case ent.ll == nil: + // Case IV, not A nor B ent.setMRU(a.t1) } } @@ -157,11 +156,11 @@ func (a *ARC) replace(ent *entry) { lru.ghost = true a.len-- lru.setMRU(a.b1) - } else { - lru := a.t2.Back().Value.(*entry) - lru.value = nil - lru.ghost = true - a.len-- - lru.setMRU(a.b2) + return } + lru := a.t2.Back().Value.(*entry) + lru.value = nil + lru.ghost = true + a.len-- + lru.setMRU(a.b2) } diff --git a/arc_test.go b/arc_test.go index 4473b5d..4c607fb 100644 --- a/arc_test.go +++ b/arc_test.go @@ -2,58 +2,102 @@ package arc import "testing" -func TestBasic(t *testing.T) { +func TestInsertion(t *testing.T) { cache := New(3) - if cache.Len() != 0 { - t.Error("Empty cache should have length 0") + if got, want := cache.Len(), 0; got != want { + t.Errorf("empty cache.Len(): got %d want %d", cache.Len(), want) } - cache.Put("Hello", "World") - if cache.Len() != 1 { - t.Error("Cache should have length 1") - } + const ( + k1 = "Hello" + k2 = "Hallo" + k3 = "Ciao" + k4 = "Salut" - var val interface{} - var ok bool + v1 = "World" + v2 = "Worlds" + v3 = "Welt" + ) - if val, ok = cache.Get("Hello"); val != "World" || ok != true { - t.Error("Didn't set \"Hello\" to \"World\"") + // Insert the first value + cache.Put(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) } - cache.Put("Hello", "World1") - if cache.Len() != 1 { - t.Error("Inserting the same entry multiple times shouldn't increase cache size") + // Replace existing value for a given key + cache.Put(k1, v2) + if got, want := cache.Len(), 1; got != want { + t.Errorf("re-insertion: cache.Len(): got %d want %d", cache.Len(), want) + } + 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) } - if val, ok = cache.Get("Hello"); val != "World1" || ok != true { - t.Error("Didn't update \"Hello\" to \"World1\"") + // Add a second different key + cache.Put(k2, v3) + 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) } - cache.Put("Hallo", "Welt") - if cache.Len() != 2 { - t.Error("Inserting two different entries should result into lenght=2") + // Fill cache + cache.Put(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) } - if val, ok = cache.Get("Hallo"); val != "Welt" || ok != true { - t.Error("Didn't set \"Hallo\" to \"Welt\"") + // Exceed size, this should not exceed size: + cache.Put(k4, v1) + 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) } } -func TestBasicReplace(t *testing.T) { - cache := New(3) +func TestEviction(t *testing.T) { + size := 3 + cache := New(size) + if got, want := cache.Len(), 0; got != want { + t.Errorf("empty cache.Len(): got %d want %d", cache.Len(), want) + } - cache.Put("Hello", "Hallo") - cache.Put("World", "Welt") - cache.Get("World") - cache.Put("Cache", "Cache") - cache.Put("Replace", "Ersetzen") + tests := []struct { + k, v string + }{ + {"k1", "v1"}, + {"k2", "v2"}, + {"k3", "v3"}, + {"k4", "v4"}, + } + for i, tt := range tests[:size] { + cache.Put(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.Put(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) + } - value, ok := cache.Get("World") - if !ok || value != "Welt" { - t.Error("ARC should have replaced \"Hello\"") + // Check that LRU got evicted: + if got, ok := cache.Get(tests[0].k); ok || got != nil { + t.Errorf("cache.Get(%v): got (%v,%t) want (,true)", tests[0].k, got, ok) } - if cache.Len() != 3 { - t.Error("ARC should have a maximum size of 3") + 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) + } } }