From 1cf72b2f97db2121414d2467e81245a45ec0de50 Mon Sep 17 00:00:00 2001 From: Antonio Jesus Navarro Perez Date: Wed, 19 Jul 2017 16:35:31 +0200 Subject: [PATCH] cache: Change cache implementation from FIFO to LRU --- plumbing/cache/object.go | 91 +++++++++++++++++------------ plumbing/cache/object_test.go | 10 ++-- plumbing/cache/queue.go | 46 --------------- plumbing/format/packfile/decoder.go | 2 +- 4 files changed, 61 insertions(+), 88 deletions(-) delete mode 100644 plumbing/cache/queue.go diff --git a/plumbing/cache/object.go b/plumbing/cache/object.go index 44238ce11..994d896cf 100644 --- a/plumbing/cache/object.go +++ b/plumbing/cache/object.go @@ -1,68 +1,87 @@ package cache -import "gopkg.in/src-d/go-git.v4/plumbing" +import ( + "github.com/golang/groupcache/lru" + "gopkg.in/src-d/go-git.v4/plumbing" +) const ( - initialQueueSize = 20 - MaxSize = 10 * MiByte + MaxSize = 96 * MiByte ) -type ObjectFIFO struct { - objects map[plumbing.Hash]plumbing.EncodedObject - order *queue +type ObjectLRU struct { + // Max file size allowed into this cache + MaxSize FileSize - maxSize FileSize + cache *lru.Cache actualSize FileSize } -// NewObjectFIFO returns an Object cache that keeps the newest objects that fit +// NewObjectLRU returns an Object cache that keeps the most used objects that fit // into the specific memory size -func NewObjectFIFO(size FileSize) *ObjectFIFO { - return &ObjectFIFO{ - objects: make(map[plumbing.Hash]plumbing.EncodedObject), - order: newQueue(initialQueueSize), - maxSize: size, +func NewObjectLRU(size FileSize) *ObjectLRU { + olru := &ObjectLRU{ + MaxSize: size, + } + + lc := lru.New(0) + lc.OnEvicted = func(key lru.Key, value interface{}) { + obj := getAsObject(value) + olru.actualSize -= FileSize(obj.Size()) } + olru.cache = lc + + return olru +} + +func getAsObject(value interface{}) plumbing.EncodedObject { + v, ok := value.(plumbing.EncodedObject) + if !ok { + panic("unreachable") + } + + return v } // Add adds a new object to the cache. If the object size is greater than the -// cache size, the object is not added. -func (c *ObjectFIFO) Add(o plumbing.EncodedObject) { +// cache size, the less used objects will be discarded until the cache fits into +// the specified size again. +func (c *ObjectLRU) Add(o plumbing.EncodedObject) { // if the size of the object is bigger or equal than the cache size, // skip it - if FileSize(o.Size()) >= c.maxSize { + if FileSize(o.Size()) >= c.MaxSize { return } - // if the object is into the cache, do not add it again - if _, ok := c.objects[o.Hash()]; ok { - return + oldLen := c.cache.Len() + c.cache.Add(o.Hash(), o) + if oldLen < c.cache.Len() { + c.actualSize += FileSize(o.Size()) } - // delete the oldest object if cache is full - if c.actualSize >= c.maxSize { - h := c.order.Pop() - o := c.objects[h] - if o != nil { - c.actualSize -= FileSize(o.Size()) - delete(c.objects, h) + if c.actualSize > c.MaxSize { + for { + if c.actualSize <= c.MaxSize { + break + } + + c.cache.RemoveOldest() } } - - c.objects[o.Hash()] = o - c.order.Push(o.Hash()) - c.actualSize += FileSize(o.Size()) } // Get returns an object by his hash. If the object is not found in the cache, it // returns nil -func (c *ObjectFIFO) Get(k plumbing.Hash) plumbing.EncodedObject { - return c.objects[k] +func (c *ObjectLRU) Get(k plumbing.Hash) plumbing.EncodedObject { + val, ok := c.cache.Get(k) + if !ok { + return nil + } + + return getAsObject(val) } // Clear the content of this object cache -func (c *ObjectFIFO) Clear() { - c.objects = make(map[plumbing.Hash]plumbing.EncodedObject) - c.order = newQueue(initialQueueSize) - c.actualSize = 0 +func (c *ObjectLRU) Clear() { + c.cache.Clear() } diff --git a/plumbing/cache/object_test.go b/plumbing/cache/object_test.go index 80cd17b7e..c243cadfb 100644 --- a/plumbing/cache/object_test.go +++ b/plumbing/cache/object_test.go @@ -12,7 +12,7 @@ import ( func Test(t *testing.T) { TestingT(t) } type ObjectSuite struct { - c *ObjectFIFO + c *ObjectLRU aObject plumbing.EncodedObject bObject plumbing.EncodedObject cObject plumbing.EncodedObject @@ -27,7 +27,7 @@ func (s *ObjectSuite) SetUpTest(c *C) { s.cObject = newObject("cccccccccccccccccccccccccccccccccccccccc", 1*Byte) s.dObject = newObject("dddddddddddddddddddddddddddddddddddddddd", 1*Byte) - s.c = NewObjectFIFO(2 * Byte) + s.c = NewObjectLRU(2 * Byte) } func (s *ObjectSuite) TestAdd_SameObject(c *C) { @@ -43,16 +43,16 @@ func (s *ObjectSuite) TestAdd_BigObject(c *C) { c.Assert(s.c.actualSize, Equals, 0*KiByte) c.Assert(s.c.actualSize, Equals, 0*MiByte) c.Assert(s.c.actualSize, Equals, 0*GiByte) - c.Assert(len(s.c.objects), Equals, 0) + c.Assert(s.c.cache.Len(), Equals, 0) } func (s *ObjectSuite) TestAdd_CacheOverflow(c *C) { s.c.Add(s.aObject) c.Assert(s.c.actualSize, Equals, 1*Byte) s.c.Add(s.cObject) - c.Assert(len(s.c.objects), Equals, 2) + c.Assert(s.c.cache.Len(), Equals, 2) s.c.Add(s.dObject) - c.Assert(len(s.c.objects), Equals, 2) + c.Assert(s.c.cache.Len(), Equals, 2) c.Assert(s.c.Get(s.aObject.Hash()), IsNil) c.Assert(s.c.Get(s.cObject.Hash()), NotNil) diff --git a/plumbing/cache/queue.go b/plumbing/cache/queue.go deleted file mode 100644 index 85413e0b7..000000000 --- a/plumbing/cache/queue.go +++ /dev/null @@ -1,46 +0,0 @@ -package cache - -import "gopkg.in/src-d/go-git.v4/plumbing" - -// queue is a basic FIFO queue based on a circular list that resize as needed. -type queue struct { - elements []plumbing.Hash - size int - head int - tail int - count int -} - -// newQueue returns a queue with the specified initial size -func newQueue(size int) *queue { - return &queue{ - elements: make([]plumbing.Hash, size), - size: size, - } -} - -// Push adds a node to the queue. -func (q *queue) Push(h plumbing.Hash) { - if q.head == q.tail && q.count > 0 { - elements := make([]plumbing.Hash, len(q.elements)+q.size) - copy(elements, q.elements[q.head:]) - copy(elements[len(q.elements)-q.head:], q.elements[:q.head]) - q.head = 0 - q.tail = len(q.elements) - q.elements = elements - } - q.elements[q.tail] = h - q.tail = (q.tail + 1) % len(q.elements) - q.count++ -} - -// Pop removes and returns a Hash from the queue in first to last order. -func (q *queue) Pop() plumbing.Hash { - if q.count == 0 { - return plumbing.ZeroHash - } - node := q.elements[q.head] - q.head = (q.head + 1) % len(q.elements) - q.count-- - return node -} diff --git a/plumbing/format/packfile/decoder.go b/plumbing/format/packfile/decoder.go index 21ddbbfdc..a5b415709 100644 --- a/plumbing/format/packfile/decoder.go +++ b/plumbing/format/packfile/decoder.go @@ -109,7 +109,7 @@ func NewDecoderForType(s *Scanner, o storer.EncodedObjectStorer, offsetToType: make(map[int64]plumbing.ObjectType, 0), decoderType: t, - cache: cache.NewObjectFIFO(cache.MaxSize), + cache: cache.NewObjectLRU(cache.MaxSize), }, nil }