Skip to content

Commit fb42b90

Browse files
author
John Mason
committed
Use LRU cache
1 parent e15c23e commit fb42b90

File tree

5 files changed

+177
-20
lines changed

5 files changed

+177
-20
lines changed

index/indexdata.go

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,11 +105,27 @@ type indexData struct {
105105
rawConfigMasks []uint8
106106

107107
// Cache for docMatchTree objects
108-
docMatchTreeCache docMatchTreeCache
108+
docMatchTreeCache *docMatchTreeCache
109109
}
110110

111-
// docMatchTreeCache is a cache for docMatchTree objects so they don't need to be recomputed
112-
type docMatchTreeCache map[struct{ field, value string }]*docMatchTree
111+
// docMatchTreeCache is an LRU cache for docMatchTree objects so they don't need to be recomputed.
112+
type docMatchTreeCache struct {
113+
cache *LRUCache[[2]string, *docMatchTree]
114+
}
115+
116+
func newDocMatchTreeCache(maxEntries int) *docMatchTreeCache {
117+
return &docMatchTreeCache{
118+
cache: NewLRUCache[[2]string, *docMatchTree](maxEntries),
119+
}
120+
}
121+
122+
func (c *docMatchTreeCache) get(field, value string) (*docMatchTree, bool) {
123+
return c.cache.Get([2]string{field, value})
124+
}
125+
126+
func (c *docMatchTreeCache) add(field, value string, dmt *docMatchTree) {
127+
c.cache.Add([2]string{field, value}, dmt)
128+
}
113129

114130
type symbolData struct {
115131
// symContent stores Symbol.Sym and Symbol.Parent.

index/lrucache.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package index
2+
3+
import (
4+
"container/list"
5+
)
6+
7+
type LRUCache[K comparable, V any] struct {
8+
maxEntries int
9+
ll *list.List
10+
cache map[K]*list.Element
11+
}
12+
13+
type entry[K comparable, V any] struct {
14+
key K
15+
value V
16+
}
17+
18+
// NewLRUCache creates a new LRUCache with the given max size.
19+
func NewLRUCache[K comparable, V any](maxEntries int) *LRUCache[K, V] {
20+
return &LRUCache[K, V]{
21+
maxEntries: maxEntries,
22+
ll: list.New(),
23+
cache: make(map[K]*list.Element),
24+
}
25+
}
26+
27+
func (c *LRUCache[K, V]) Get(key K) (V, bool) {
28+
if ele, ok := c.cache[key]; ok {
29+
c.ll.MoveToFront(ele)
30+
return ele.Value.(*entry[K, V]).value, true
31+
}
32+
var zero V
33+
return zero, false
34+
}
35+
36+
func (c *LRUCache[K, V]) Add(key K, value V) {
37+
if ele, ok := c.cache[key]; ok {
38+
c.ll.MoveToFront(ele)
39+
ele.Value.(*entry[K, V]).value = value
40+
return
41+
}
42+
ele := c.ll.PushFront(&entry[K, V]{key, value})
43+
c.cache[key] = ele
44+
if c.maxEntries != 0 && c.ll.Len() > c.maxEntries {
45+
c.removeOldest()
46+
}
47+
}
48+
49+
func (c *LRUCache[K, V]) Remove(key K) {
50+
if ele, ok := c.cache[key]; ok {
51+
c.removeElement(ele)
52+
}
53+
}
54+
55+
func (c *LRUCache[K, V]) removeOldest() {
56+
if ele := c.ll.Back(); ele != nil {
57+
c.removeElement(ele)
58+
}
59+
}
60+
61+
func (c *LRUCache[K, V]) removeElement(e *list.Element) {
62+
c.ll.Remove(e)
63+
kv := e.Value.(*entry[K, V])
64+
delete(c.cache, kv.key)
65+
}
66+
67+
func (c *LRUCache[K, V]) Len() int {
68+
return c.ll.Len()
69+
}

index/lrucache_test.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package index
2+
3+
import (
4+
"testing"
5+
)
6+
7+
func TestLRUCache_Basic(t *testing.T) {
8+
cache := NewLRUCache[string, int](2)
9+
10+
// Add and Get
11+
cache.Add("a", 1)
12+
cache.Add("b", 2)
13+
if v, ok := cache.Get("a"); !ok || v != 1 {
14+
t.Errorf("expected 1, got %v", v)
15+
}
16+
if v, ok := cache.Get("b"); !ok || v != 2 {
17+
t.Errorf("expected 2, got %v", v)
18+
}
19+
20+
// Add triggers eviction
21+
cache.Add("c", 3)
22+
if _, ok := cache.Get("a"); ok {
23+
t.Errorf("expected 'a' to be evicted")
24+
}
25+
if v, ok := cache.Get("b"); !ok || v != 2 {
26+
t.Errorf("expected 2, got %v", v)
27+
}
28+
if v, ok := cache.Get("c"); !ok || v != 3 {
29+
t.Errorf("expected 3, got %v", v)
30+
}
31+
32+
// Access order updates
33+
cache.Get("b")
34+
cache.Add("d", 4)
35+
if _, ok := cache.Get("c"); ok {
36+
t.Errorf("expected 'c' to be evicted after 'b' was used")
37+
}
38+
if v, ok := cache.Get("b"); !ok || v != 2 {
39+
t.Errorf("expected 2, got %v", v)
40+
}
41+
if v, ok := cache.Get("d"); !ok || v != 4 {
42+
t.Errorf("expected 4, got %v", v)
43+
}
44+
}
45+
46+
func TestLRUCache_Remove(t *testing.T) {
47+
cache := NewLRUCache[string, int](2)
48+
cache.Add("a", 1)
49+
cache.Add("b", 2)
50+
cache.Remove("a")
51+
if _, ok := cache.Get("a"); ok {
52+
t.Errorf("expected 'a' to be removed")
53+
}
54+
if v, ok := cache.Get("b"); !ok || v != 2 {
55+
t.Errorf("expected 2, got %v", v)
56+
}
57+
}
58+
59+
func TestLRUCache_Len(t *testing.T) {
60+
cache := NewLRUCache[string, int](2)
61+
if cache.Len() != 0 {
62+
t.Errorf("expected len 0, got %d", cache.Len())
63+
}
64+
cache.Add("a", 1)
65+
if cache.Len() != 1 {
66+
t.Errorf("expected len 1, got %d", cache.Len())
67+
}
68+
cache.Add("b", 2)
69+
if cache.Len() != 2 {
70+
t.Errorf("expected len 2, got %d", cache.Len())
71+
}
72+
cache.Add("c", 3)
73+
if cache.Len() != 2 {
74+
t.Errorf("expected len 2 after eviction, got %d", cache.Len())
75+
}
76+
}

index/matchtree.go

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -974,7 +974,7 @@ func (d *indexData) newMatchTree(q query.Q, opt matchTreeOpt) (matchTree, error)
974974
}
975975

976976
if d.docMatchTreeCache == nil {
977-
d.docMatchTreeCache = make(docMatchTreeCache)
977+
d.docMatchTreeCache = newDocMatchTreeCache(64)
978978
}
979979

980980
switch s := q.(type) {
@@ -1061,9 +1061,8 @@ func (d *indexData) newMatchTree(q query.Q, opt matchTreeOpt) (matchTree, error)
10611061

10621062
case *query.Meta:
10631063
checksum := queryMetaChecksum(s.Field, s.Value)
1064-
cacheKey := struct{ field, value string }{"Meta", checksum}
1065-
1066-
if cached, ok := d.docMatchTreeCache[cacheKey]; ok {
1064+
cacheKeyField := "Meta"
1065+
if cached, ok := d.docMatchTreeCache.get(cacheKeyField, checksum); ok {
10671066
return cached, nil
10681067
}
10691068

@@ -1087,7 +1086,7 @@ func (d *indexData) newMatchTree(q query.Q, opt matchTreeOpt) (matchTree, error)
10871086
return reposWant[repoIdx]
10881087
},
10891088
}
1090-
d.docMatchTreeCache[cacheKey] = mt
1089+
d.docMatchTreeCache.add(cacheKeyField, checksum, mt)
10911090
return mt, nil
10921091

10931092
case *query.Substring:
@@ -1217,9 +1216,8 @@ func (d *indexData) newMatchTree(q query.Q, opt matchTreeOpt) (matchTree, error)
12171216

12181217
case *query.RepoIDs:
12191218
checksum := queryRepoIdsChecksum(d.repoMetaData)
1220-
cacheKey := struct{ field, value string }{"RepoIDs", checksum}
1221-
1222-
if cached, ok := d.docMatchTreeCache[cacheKey]; ok {
1219+
cacheKeyField := "RepoIDs"
1220+
if cached, ok := d.docMatchTreeCache.get(cacheKeyField, checksum); ok {
12231221
return cached, nil
12241222
}
12251223

@@ -1237,7 +1235,7 @@ func (d *indexData) newMatchTree(q query.Q, opt matchTreeOpt) (matchTree, error)
12371235
return reposWant[d.repos[docID]]
12381236
},
12391237
}
1240-
d.docMatchTreeCache[cacheKey] = mt
1238+
d.docMatchTreeCache.add(cacheKeyField, checksum, mt)
12411239
return mt, nil
12421240

12431241
case *query.Repo:

index/matchtree_test.go

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -380,10 +380,9 @@ func TestRepoIDs(t *testing.T) {
380380

381381
// Check that the docMatchTree cache is populated correctly
382382
checksum := queryRepoIdsChecksum(d.repoMetaData)
383-
cacheKey := struct{ field, value string }{"RepoIDs", checksum}
384-
385-
if _, ok := d.docMatchTreeCache[cacheKey]; !ok {
386-
t.Errorf("expected docMatchTreeCache to be populated for key %q", cacheKey)
383+
cacheKeyField := "RepoIDs"
384+
if _, ok := d.docMatchTreeCache.get(cacheKeyField, checksum); !ok {
385+
t.Errorf("expected docMatchTreeCache to be populated for key (%q, %q)", cacheKeyField, checksum)
387386
}
388387

389388
want := []uint32{2, 4, 5}
@@ -459,10 +458,9 @@ func TestMetaQueryMatchTree(t *testing.T) {
459458

460459
// Check that the docMatchTree cache is populated correctly
461460
checksum := queryMetaChecksum("license", regexp.MustCompile("M.T"))
462-
cacheKey := struct{ field, value string }{"Meta", checksum}
463-
464-
if _, ok := d.docMatchTreeCache[cacheKey]; !ok {
465-
t.Errorf("expected docMatchTreeCache to be populated for key %q", cacheKey)
461+
cacheKeyField := "Meta"
462+
if _, ok := d.docMatchTreeCache.get(cacheKeyField, checksum); !ok {
463+
t.Errorf("expected docMatchTreeCache to be populated for key (%q, %q)", cacheKeyField, checksum)
466464
}
467465

468466
var matched []uint32

0 commit comments

Comments
 (0)