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

Commit ba0f659

Browse files
authored
Merge pull request #916 from jfontan/improvement/memory-consumption-new-packfile-parser
Improvement/memory consumption new packfile parser
2 parents a28c2ce + eb2aa9b commit ba0f659

File tree

4 files changed

+276
-53
lines changed

4 files changed

+276
-53
lines changed

plumbing/cache/buffer_lru.go

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

plumbing/cache/buffer_test.go

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
package cache
2+
3+
import (
4+
"sync"
5+
6+
. "gopkg.in/check.v1"
7+
)
8+
9+
type BufferSuite struct {
10+
c map[string]Buffer
11+
aBuffer []byte
12+
bBuffer []byte
13+
cBuffer []byte
14+
dBuffer []byte
15+
eBuffer []byte
16+
}
17+
18+
var _ = Suite(&BufferSuite{})
19+
20+
func (s *BufferSuite) SetUpTest(c *C) {
21+
s.aBuffer = []byte("a")
22+
s.bBuffer = []byte("bbb")
23+
s.cBuffer = []byte("c")
24+
s.dBuffer = []byte("d")
25+
s.eBuffer = []byte("ee")
26+
27+
s.c = make(map[string]Buffer)
28+
s.c["two_bytes"] = NewBufferLRU(2 * Byte)
29+
s.c["default_lru"] = NewBufferLRUDefault()
30+
}
31+
32+
func (s *BufferSuite) TestPutSameBuffer(c *C) {
33+
for _, o := range s.c {
34+
o.Put(1, s.aBuffer)
35+
o.Put(1, s.aBuffer)
36+
_, ok := o.Get(1)
37+
c.Assert(ok, Equals, true)
38+
}
39+
}
40+
41+
func (s *BufferSuite) TestPutBigBuffer(c *C) {
42+
for _, o := range s.c {
43+
o.Put(1, s.bBuffer)
44+
_, ok := o.Get(2)
45+
c.Assert(ok, Equals, false)
46+
}
47+
}
48+
49+
func (s *BufferSuite) TestPutCacheOverflow(c *C) {
50+
// this test only works with an specific size
51+
o := s.c["two_bytes"]
52+
53+
o.Put(1, s.aBuffer)
54+
o.Put(2, s.cBuffer)
55+
o.Put(3, s.dBuffer)
56+
57+
obj, ok := o.Get(1)
58+
c.Assert(ok, Equals, false)
59+
c.Assert(obj, IsNil)
60+
obj, ok = o.Get(2)
61+
c.Assert(ok, Equals, true)
62+
c.Assert(obj, NotNil)
63+
obj, ok = o.Get(3)
64+
c.Assert(ok, Equals, true)
65+
c.Assert(obj, NotNil)
66+
}
67+
68+
func (s *BufferSuite) TestEvictMultipleBuffers(c *C) {
69+
o := s.c["two_bytes"]
70+
71+
o.Put(1, s.cBuffer)
72+
o.Put(2, s.dBuffer) // now cache is full with two objects
73+
o.Put(3, s.eBuffer) // this put should evict all previous objects
74+
75+
obj, ok := o.Get(1)
76+
c.Assert(ok, Equals, false)
77+
c.Assert(obj, IsNil)
78+
obj, ok = o.Get(2)
79+
c.Assert(ok, Equals, false)
80+
c.Assert(obj, IsNil)
81+
obj, ok = o.Get(3)
82+
c.Assert(ok, Equals, true)
83+
c.Assert(obj, NotNil)
84+
}
85+
86+
func (s *BufferSuite) TestClear(c *C) {
87+
for _, o := range s.c {
88+
o.Put(1, s.aBuffer)
89+
o.Clear()
90+
obj, ok := o.Get(1)
91+
c.Assert(ok, Equals, false)
92+
c.Assert(obj, IsNil)
93+
}
94+
}
95+
96+
func (s *BufferSuite) TestConcurrentAccess(c *C) {
97+
for _, o := range s.c {
98+
var wg sync.WaitGroup
99+
100+
for i := 0; i < 1000; i++ {
101+
wg.Add(3)
102+
go func(i int) {
103+
o.Put(int64(i), []byte{00})
104+
wg.Done()
105+
}(i)
106+
107+
go func(i int) {
108+
if i%30 == 0 {
109+
o.Clear()
110+
}
111+
wg.Done()
112+
}(i)
113+
114+
go func(i int) {
115+
o.Get(int64(i))
116+
wg.Done()
117+
}(i)
118+
}
119+
120+
wg.Wait()
121+
}
122+
}
123+
124+
func (s *BufferSuite) TestDefaultLRU(c *C) {
125+
defaultLRU := s.c["default_lru"].(*BufferLRU)
126+
127+
c.Assert(defaultLRU.MaxSize, Equals, DefaultMaxSize)
128+
}

plumbing/cache/common.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,16 @@ type Object interface {
2424
// Clear clears every object from the cache.
2525
Clear()
2626
}
27+
28+
// Buffer is an interface to a buffer cache.
29+
type Buffer interface {
30+
// Put puts a buffer into the cache. If the buffer is already in the cache,
31+
// it will be marked as used. Otherwise, it will be inserted. Buffer might
32+
// be evicted to make room for the new one.
33+
Put(key int64, slice []byte)
34+
// Get returns a buffer by its key. It marks the buffer as used. If the
35+
// buffer is not in the cache, (nil, false) will be returned.
36+
Get(key int64) ([]byte, bool)
37+
// Clear clears every object from the cache.
38+
Clear()
39+
}

0 commit comments

Comments
 (0)