diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 45f6224c7..48c3e99d4 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -30,6 +30,10 @@ "Comment": "null-15", "Rev": "12e4b4183793ac4b061921e7980845e750679fd0" }, + { + "ImportPath": "github.com/AudriusButkevicius/lfu-go", + "Rev": "164bcecceb92fd6037f4d18a8d97b495ec6ef669" + }, { "ImportPath": "github.com/bkaradzic/go-lz4", "Rev": "93a831dcee242be64a9cc9803dda84af25932de7" diff --git a/Godeps/_workspace/src/github.com/AudriusButkevicius/lfu-go/LICENSE b/Godeps/_workspace/src/github.com/AudriusButkevicius/lfu-go/LICENSE new file mode 100644 index 000000000..431a0037f --- /dev/null +++ b/Godeps/_workspace/src/github.com/AudriusButkevicius/lfu-go/LICENSE @@ -0,0 +1,19 @@ +Copyright (C) 2012 Dave Grijalva + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/AudriusButkevicius/lfu-go/README.md b/Godeps/_workspace/src/github.com/AudriusButkevicius/lfu-go/README.md new file mode 100644 index 000000000..2a0742586 --- /dev/null +++ b/Godeps/_workspace/src/github.com/AudriusButkevicius/lfu-go/README.md @@ -0,0 +1,19 @@ +A simple LFU cache for golang. Based on the paper [An O(1) algorithm for implementing the LFU cache eviction scheme](http://dhruvbird.com/lfu.pdf). + +Usage: + +```go +import "github.com/dgrijalva/lfu-go" + +// Make a new thing +c := lfu.New() + +// Set some values +c.Set("myKey", myValue) + +// Retrieve some values +myValue = c.Get("myKey") + +// Evict some values +c.Evict(1) +``` \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/AudriusButkevicius/lfu-go/lfu.go b/Godeps/_workspace/src/github.com/AudriusButkevicius/lfu-go/lfu.go new file mode 100644 index 000000000..cfe387156 --- /dev/null +++ b/Godeps/_workspace/src/github.com/AudriusButkevicius/lfu-go/lfu.go @@ -0,0 +1,156 @@ +package lfu + +import ( + "container/list" + "sync" +) + +type Eviction struct { + Key string + Value interface{} +} + +type Cache struct { + // If len > UpperBound, cache will automatically evict + // down to LowerBound. If either value is 0, this behavior + // is disabled. + UpperBound int + LowerBound int + values map[string]*cacheEntry + freqs *list.List + len int + lock *sync.Mutex + EvictionChannel chan<- Eviction +} + +type cacheEntry struct { + key string + value interface{} + freqNode *list.Element +} + +type listEntry struct { + entries map[*cacheEntry]byte + freq int +} + +func New() *Cache { + c := new(Cache) + c.values = make(map[string]*cacheEntry) + c.freqs = list.New() + c.lock = new(sync.Mutex) + return c +} + +func (c *Cache) Get(key string) interface{} { + c.lock.Lock() + defer c.lock.Unlock() + if e, ok := c.values[key]; ok { + c.increment(e) + return e.value + } + return nil +} + +func (c *Cache) Set(key string, value interface{}) { + c.lock.Lock() + defer c.lock.Unlock() + if e, ok := c.values[key]; ok { + // value already exists for key. overwrite + e.value = value + c.increment(e) + } else { + // value doesn't exist. insert + e := new(cacheEntry) + e.key = key + e.value = value + c.values[key] = e + c.increment(e) + c.len++ + // bounds mgmt + if c.UpperBound > 0 && c.LowerBound > 0 { + if c.len > c.UpperBound { + c.evict(c.len - c.LowerBound) + } + } + } +} + +func (c *Cache) Len() int { + c.lock.Lock() + defer c.lock.Unlock() + return c.len +} + +func (c *Cache) Evict(count int) int { + c.lock.Lock() + defer c.lock.Unlock() + return c.evict(count) +} + +func (c *Cache) evict(count int) int { + // No lock here so it can be called + // from within the lock (during Set) + var evicted int + for i := 0; i < count; { + if place := c.freqs.Front(); place != nil { + for entry, _ := range place.Value.(*listEntry).entries { + if i < count { + if c.EvictionChannel != nil { + c.EvictionChannel <- Eviction{ + Key: entry.key, + Value: entry.value, + } + } + delete(c.values, entry.key) + c.remEntry(place, entry) + evicted++ + c.len-- + i++ + } + } + } + } + return evicted +} + +func (c *Cache) increment(e *cacheEntry) { + currentPlace := e.freqNode + var nextFreq int + var nextPlace *list.Element + if currentPlace == nil { + // new entry + nextFreq = 1 + nextPlace = c.freqs.Front() + } else { + // move up + nextFreq = currentPlace.Value.(*listEntry).freq + 1 + nextPlace = currentPlace.Next() + } + + if nextPlace == nil || nextPlace.Value.(*listEntry).freq != nextFreq { + // create a new list entry + li := new(listEntry) + li.freq = nextFreq + li.entries = make(map[*cacheEntry]byte) + if currentPlace != nil { + nextPlace = c.freqs.InsertAfter(li, currentPlace) + } else { + nextPlace = c.freqs.PushFront(li) + } + } + e.freqNode = nextPlace + nextPlace.Value.(*listEntry).entries[e] = 1 + if currentPlace != nil { + // remove from current position + c.remEntry(currentPlace, e) + } +} + +func (c *Cache) remEntry(place *list.Element, entry *cacheEntry) { + entries := place.Value.(*listEntry).entries + delete(entries, entry) + if len(entries) == 0 { + c.freqs.Remove(place) + } +} diff --git a/Godeps/_workspace/src/github.com/AudriusButkevicius/lfu-go/lfu_test.go b/Godeps/_workspace/src/github.com/AudriusButkevicius/lfu-go/lfu_test.go new file mode 100644 index 000000000..61d97c0a4 --- /dev/null +++ b/Godeps/_workspace/src/github.com/AudriusButkevicius/lfu-go/lfu_test.go @@ -0,0 +1,68 @@ +package lfu + +import ( + "fmt" + "testing" +) + +func TestLFU(t *testing.T) { + c := New() + c.Set("a", "a") + if v := c.Get("a"); v != "a" { + t.Errorf("Value was not saved: %v != 'a'", v) + } + if l := c.Len(); l != 1 { + t.Errorf("Length was not updated: %v != 1", l) + } + + c.Set("b", "b") + if v := c.Get("b"); v != "b" { + t.Errorf("Value was not saved: %v != 'b'", v) + } + if l := c.Len(); l != 2 { + t.Errorf("Length was not updated: %v != 2", l) + } + + c.Get("a") + evicted := c.Evict(1) + if v := c.Get("a"); v != "a" { + t.Errorf("Value was improperly evicted: %v != 'a'", v) + } + if v := c.Get("b"); v != nil { + t.Errorf("Value was not evicted: %v", v) + } + if l := c.Len(); l != 1 { + t.Errorf("Length was not updated: %v != 1", l) + } + if evicted != 1 { + t.Errorf("Number of evicted items is wrong: %v != 1", evicted) + } +} + +func TestBoundsMgmt(t *testing.T) { + c := New() + c.UpperBound = 10 + c.LowerBound = 5 + + for i := 0; i < 100; i++ { + c.Set(fmt.Sprintf("%v", i), i) + } + if c.Len() > 10 { + t.Errorf("Bounds management failed to evict properly: %v", c.Len()) + } +} + +func TestEviction(t *testing.T) { + ch := make(chan Eviction, 1) + + c := New() + c.EvictionChannel = ch + c.Set("a", "b") + c.Evict(1) + + ev := <-ch + + if ev.Key != "a" || ev.Value.(string) != "b" { + t.Error("Incorrect item") + } +}