Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Various Improvements #3

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 42 additions & 43 deletions arc.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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{
Expand All @@ -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.
Expand All @@ -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.
Expand All @@ -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

Expand All @@ -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

Expand All @@ -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)
}
}
Expand All @@ -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)
}
110 changes: 77 additions & 33 deletions arc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 (<nil>,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)
}
}
}