Skip to content
This repository was archived by the owner on Sep 11, 2020. It is now read-only.

Commit 8c57f96

Browse files
authored
Merge pull request #514 from smola/use-cache-delta
cache: reuse object cache for delta resolution, use LRU policy
2 parents 854ffa1 + ae1c4f3 commit 8c57f96

File tree

6 files changed

+154
-113
lines changed

6 files changed

+154
-113
lines changed

plumbing/cache/common.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,14 @@ const (
1111

1212
type FileSize int64
1313

14+
// Object is an interface to a object cache.
1415
type Object interface {
15-
Add(o plumbing.EncodedObject)
16-
Get(k plumbing.Hash) plumbing.EncodedObject
16+
// Put puts the given object into the cache. Whether this object will
17+
// actually be put into the cache or not is implementation specific.
18+
Put(o plumbing.EncodedObject)
19+
// Get gets an object from the cache given its hash. The second return value
20+
// is true if the object was returned, and false otherwise.
21+
Get(k plumbing.Hash) (plumbing.EncodedObject, bool)
22+
// Clear clears every object from the cache.
1723
Clear()
1824
}

plumbing/cache/object.go

Lines changed: 0 additions & 68 deletions
This file was deleted.

plumbing/cache/object_lru.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package cache
2+
3+
import (
4+
"container/list"
5+
6+
"gopkg.in/src-d/go-git.v4/plumbing"
7+
)
8+
9+
// ObjectLRU implements an object cache with an LRU eviction policy and a
10+
// maximum size (measured in object size).
11+
type ObjectLRU struct {
12+
MaxSize FileSize
13+
14+
actualSize FileSize
15+
ll *list.List
16+
cache map[interface{}]*list.Element
17+
}
18+
19+
// NewObjectLRU creates a new ObjectLRU with the given maximum size. The maximum
20+
// size will never be exceeded.
21+
func NewObjectLRU(maxSize FileSize) *ObjectLRU {
22+
return &ObjectLRU{MaxSize: maxSize}
23+
}
24+
25+
// Put puts an object into the cache. If the object is already in the cache, it
26+
// will be marked as used. Otherwise, it will be inserted. A single object might
27+
// be evicted to make room for the new object.
28+
func (c *ObjectLRU) Put(obj plumbing.EncodedObject) {
29+
if c.cache == nil {
30+
c.actualSize = 0
31+
c.cache = make(map[interface{}]*list.Element, 1000)
32+
c.ll = list.New()
33+
}
34+
35+
key := obj.Hash()
36+
if ee, ok := c.cache[key]; ok {
37+
c.ll.MoveToFront(ee)
38+
ee.Value = obj
39+
return
40+
}
41+
42+
objSize := FileSize(obj.Size())
43+
44+
if objSize >= c.MaxSize {
45+
return
46+
}
47+
48+
if c.actualSize+objSize > c.MaxSize {
49+
last := c.ll.Back()
50+
lastObj := last.Value.(plumbing.EncodedObject)
51+
lastSize := FileSize(lastObj.Size())
52+
53+
c.ll.Remove(last)
54+
delete(c.cache, lastObj.Hash())
55+
c.actualSize -= lastSize
56+
57+
if c.actualSize+objSize > c.MaxSize {
58+
return
59+
}
60+
}
61+
62+
ee := c.ll.PushFront(obj)
63+
c.cache[key] = ee
64+
c.actualSize += objSize
65+
}
66+
67+
// Get returns an object by its hash. It marks the object as used. If the object
68+
// is not in the cache, (nil, false) will be returned.
69+
func (c *ObjectLRU) Get(k plumbing.Hash) (plumbing.EncodedObject, bool) {
70+
ee, ok := c.cache[k]
71+
if !ok {
72+
return nil, false
73+
}
74+
75+
c.ll.MoveToFront(ee)
76+
return ee.Value.(plumbing.EncodedObject), true
77+
}
78+
79+
// Clear the content of this object cache.
80+
func (c *ObjectLRU) Clear() {
81+
c.ll = nil
82+
c.cache = nil
83+
c.actualSize = 0
84+
}

plumbing/cache/object_test.go

Lines changed: 29 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import (
1212
func Test(t *testing.T) { TestingT(t) }
1313

1414
type ObjectSuite struct {
15-
c *ObjectFIFO
15+
c Object
1616
aObject plumbing.EncodedObject
1717
bObject plumbing.EncodedObject
1818
cObject plumbing.EncodedObject
@@ -27,44 +27,44 @@ func (s *ObjectSuite) SetUpTest(c *C) {
2727
s.cObject = newObject("cccccccccccccccccccccccccccccccccccccccc", 1*Byte)
2828
s.dObject = newObject("dddddddddddddddddddddddddddddddddddddddd", 1*Byte)
2929

30-
s.c = NewObjectFIFO(2 * Byte)
30+
s.c = NewObjectLRU(2 * Byte)
3131
}
3232

33-
func (s *ObjectSuite) TestAdd_SameObject(c *C) {
34-
s.c.Add(s.aObject)
35-
c.Assert(s.c.actualSize, Equals, 1*Byte)
36-
s.c.Add(s.aObject)
37-
c.Assert(s.c.actualSize, Equals, 1*Byte)
33+
func (s *ObjectSuite) TestPutSameObject(c *C) {
34+
s.c.Put(s.aObject)
35+
s.c.Put(s.aObject)
36+
_, ok := s.c.Get(s.aObject.Hash())
37+
c.Assert(ok, Equals, true)
3838
}
3939

40-
func (s *ObjectSuite) TestAdd_BigObject(c *C) {
41-
s.c.Add(s.bObject)
42-
c.Assert(s.c.actualSize, Equals, 0*Byte)
43-
c.Assert(s.c.actualSize, Equals, 0*KiByte)
44-
c.Assert(s.c.actualSize, Equals, 0*MiByte)
45-
c.Assert(s.c.actualSize, Equals, 0*GiByte)
46-
c.Assert(len(s.c.objects), Equals, 0)
40+
func (s *ObjectSuite) TestPutBigObject(c *C) {
41+
s.c.Put(s.bObject)
42+
_, ok := s.c.Get(s.aObject.Hash())
43+
c.Assert(ok, Equals, false)
4744
}
4845

49-
func (s *ObjectSuite) TestAdd_CacheOverflow(c *C) {
50-
s.c.Add(s.aObject)
51-
c.Assert(s.c.actualSize, Equals, 1*Byte)
52-
s.c.Add(s.cObject)
53-
c.Assert(len(s.c.objects), Equals, 2)
54-
s.c.Add(s.dObject)
55-
c.Assert(len(s.c.objects), Equals, 2)
56-
57-
c.Assert(s.c.Get(s.aObject.Hash()), IsNil)
58-
c.Assert(s.c.Get(s.cObject.Hash()), NotNil)
59-
c.Assert(s.c.Get(s.dObject.Hash()), NotNil)
46+
func (s *ObjectSuite) TestPutCacheOverflow(c *C) {
47+
s.c.Put(s.aObject)
48+
s.c.Put(s.cObject)
49+
s.c.Put(s.dObject)
50+
51+
obj, ok := s.c.Get(s.aObject.Hash())
52+
c.Assert(ok, Equals, false)
53+
c.Assert(obj, IsNil)
54+
obj, ok = s.c.Get(s.cObject.Hash())
55+
c.Assert(ok, Equals, true)
56+
c.Assert(obj, NotNil)
57+
obj, ok = s.c.Get(s.dObject.Hash())
58+
c.Assert(ok, Equals, true)
59+
c.Assert(obj, NotNil)
6060
}
6161

6262
func (s *ObjectSuite) TestClear(c *C) {
63-
s.c.Add(s.aObject)
64-
c.Assert(s.c.actualSize, Equals, 1*Byte)
63+
s.c.Put(s.aObject)
6564
s.c.Clear()
66-
c.Assert(s.c.actualSize, Equals, 0*Byte)
67-
c.Assert(s.c.Get(s.aObject.Hash()), IsNil)
65+
obj, ok := s.c.Get(s.aObject.Hash())
66+
c.Assert(ok, Equals, false)
67+
c.Assert(obj, IsNil)
6868
}
6969

7070
type dummyObject struct {

plumbing/format/packfile/decoder.go

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ var (
5252
// is destroyed. The Offsets and CRCs are calculated whether an
5353
// ObjectStorer was provided or not.
5454
type Decoder struct {
55+
DeltaBaseCache cache.Object
56+
5557
s *Scanner
5658
o storer.EncodedObjectStorer
5759
tx storer.Transaction
@@ -65,8 +67,6 @@ type Decoder struct {
6567

6668
offsetToType map[int64]plumbing.ObjectType
6769
decoderType plumbing.ObjectType
68-
69-
cache cache.Object
7070
}
7171

7272
// NewDecoder returns a new Decoder that decodes a Packfile using the given
@@ -107,8 +107,6 @@ func NewDecoderForType(s *Scanner, o storer.EncodedObjectStorer,
107107
idx: NewIndex(0),
108108
offsetToType: make(map[int64]plumbing.ObjectType, 0),
109109
decoderType: t,
110-
111-
cache: cache.NewObjectFIFO(cache.MaxSize),
112110
}, nil
113111
}
114112

@@ -355,9 +353,8 @@ func (d *Decoder) fillREFDeltaObjectContent(obj plumbing.EncodedObject, ref plum
355353
return 0, err
356354
}
357355

358-
base := d.cache.Get(ref)
359-
360-
if base == nil {
356+
base, ok := d.cacheGet(ref)
357+
if !ok {
361358
base, err = d.recallByHash(ref)
362359
if err != nil {
363360
return 0, err
@@ -366,7 +363,7 @@ func (d *Decoder) fillREFDeltaObjectContent(obj plumbing.EncodedObject, ref plum
366363

367364
obj.SetType(base.Type())
368365
err = ApplyDelta(obj, base, buf.Bytes())
369-
d.cache.Add(obj)
366+
d.cachePut(obj)
370367

371368
return crc, err
372369
}
@@ -381,10 +378,10 @@ func (d *Decoder) fillOFSDeltaObjectContent(obj plumbing.EncodedObject, offset i
381378
e, ok := d.idx.LookupOffset(uint64(offset))
382379
var base plumbing.EncodedObject
383380
if ok {
384-
base = d.cache.Get(e.Hash)
381+
base, ok = d.cacheGet(e.Hash)
385382
}
386383

387-
if base == nil {
384+
if !ok {
388385
base, err = d.recallByOffset(offset)
389386
if err != nil {
390387
return 0, err
@@ -393,11 +390,27 @@ func (d *Decoder) fillOFSDeltaObjectContent(obj plumbing.EncodedObject, offset i
393390

394391
obj.SetType(base.Type())
395392
err = ApplyDelta(obj, base, buf.Bytes())
396-
d.cache.Add(obj)
393+
d.cachePut(obj)
397394

398395
return crc, err
399396
}
400397

398+
func (d *Decoder) cacheGet(h plumbing.Hash) (plumbing.EncodedObject, bool) {
399+
if d.DeltaBaseCache == nil {
400+
return nil, false
401+
}
402+
403+
return d.DeltaBaseCache.Get(h)
404+
}
405+
406+
func (d *Decoder) cachePut(obj plumbing.EncodedObject) {
407+
if d.DeltaBaseCache == nil {
408+
return
409+
}
410+
411+
d.DeltaBaseCache.Put(obj)
412+
}
413+
401414
func (d *Decoder) recallByOffset(o int64) (plumbing.EncodedObject, error) {
402415
if d.s.IsSeekable {
403416
return d.DecodeObjectAt(o)
@@ -455,7 +468,5 @@ func (d *Decoder) Index() *Index {
455468
// Close closes the Scanner. usually this mean that the whole reader is read and
456469
// discarded
457470
func (d *Decoder) Close() error {
458-
d.cache.Clear()
459-
460471
return d.s.Close()
461472
}

storage/filesystem/object.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"os"
66

77
"gopkg.in/src-d/go-git.v4/plumbing"
8+
"gopkg.in/src-d/go-git.v4/plumbing/cache"
89
"gopkg.in/src-d/go-git.v4/plumbing/format/idxfile"
910
"gopkg.in/src-d/go-git.v4/plumbing/format/objfile"
1011
"gopkg.in/src-d/go-git.v4/plumbing/format/packfile"
@@ -16,14 +17,20 @@ import (
1617
"gopkg.in/src-d/go-billy.v3"
1718
)
1819

20+
const DefaultMaxDeltaBaseCacheSize = 92 * cache.MiByte
21+
1922
type ObjectStorage struct {
23+
// DeltaBaseCache is an object cache uses to cache delta's bases when
24+
DeltaBaseCache cache.Object
25+
2026
dir *dotgit.DotGit
2127
index map[plumbing.Hash]*packfile.Index
2228
}
2329

2430
func newObjectStorage(dir *dotgit.DotGit) (ObjectStorage, error) {
2531
s := ObjectStorage{
26-
dir: dir,
32+
DeltaBaseCache: cache.NewObjectLRU(DefaultMaxDeltaBaseCacheSize),
33+
dir: dir,
2734
}
2835

2936
return s, nil
@@ -198,6 +205,7 @@ func (s *ObjectStorage) getFromPackfile(h plumbing.Hash) (plumbing.EncodedObject
198205
return nil, err
199206
}
200207

208+
d.DeltaBaseCache = s.DeltaBaseCache
201209
d.SetIndex(s.index[pack])
202210
obj, err := d.DecodeObjectAt(offset)
203211
return obj, err

0 commit comments

Comments
 (0)