Skip to content

Commit 7bc2343

Browse files
authored
feat: lru store (#43)
1 parent 974ce8b commit 7bc2343

File tree

4 files changed

+154
-1
lines changed

4 files changed

+154
-1
lines changed

go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ require (
1010
github.com/scorix/go-eccodes v0.1.5
1111
github.com/scorix/walg v0.4.0
1212
github.com/stretchr/testify v1.9.0
13+
go.opentelemetry.io/otel v1.32.0
1314
go.opentelemetry.io/otel/trace v1.32.0
1415
golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e
1516
golang.org/x/sync v0.9.0
@@ -19,7 +20,6 @@ require (
1920
github.com/davecgh/go-spew v1.1.1 // indirect
2021
github.com/kr/text v0.2.0 // indirect
2122
github.com/pmezard/go-difflib v1.0.0 // indirect
22-
go.opentelemetry.io/otel v1.32.0 // indirect
2323
golang.org/x/time v0.7.0 // indirect
2424
gopkg.in/yaml.v3 v3.0.1 // indirect
2525
)

pkg/grib2/cache/custom_test.go

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package cache_test
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"github.com/scorix/grib-go/pkg/grib2/cache"
8+
)
9+
10+
// 实现一个简单的 GridDataSource
11+
type testDataSource struct{}
12+
13+
func (t *testDataSource) ReadGridAt(ctx context.Context, index int) (float32, error) {
14+
return 1.0, nil
15+
}
16+
17+
func BenchmarkCustom_ReadGridAt_Parallel(b *testing.B) {
18+
store := cache.NewMapStore()
19+
20+
c := cache.NewCustom(
21+
func(lat, lon float32) bool {
22+
return true
23+
},
24+
&testDataSource{},
25+
store,
26+
)
27+
28+
b.ResetTimer()
29+
b.RunParallel(func(pb *testing.PB) {
30+
for pb.Next() {
31+
_, err := c.ReadGridAt(context.Background(), 1, 1, 1)
32+
if err != nil {
33+
b.Fatal(err)
34+
}
35+
}
36+
})
37+
}

pkg/grib2/cache/store.go

+63
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package cache
22

33
import (
4+
"container/list"
45
"context"
56
"sync"
67

@@ -13,6 +14,68 @@ type Store interface {
1314
Set(ctx context.Context, key int, value float32)
1415
}
1516

17+
// LRU Cache 实现
18+
type lruStore struct {
19+
mu sync.RWMutex
20+
capacity int
21+
cache map[int]*list.Element
22+
lru *list.List
23+
}
24+
25+
type entry struct {
26+
key int
27+
value float32
28+
}
29+
30+
func NewLRUStore(capacity int) Store {
31+
return &lruStore{
32+
capacity: capacity,
33+
cache: make(map[int]*list.Element),
34+
lru: list.New(),
35+
}
36+
}
37+
38+
func (l *lruStore) Get(ctx context.Context, key int) (float32, bool) {
39+
l.mu.RLock()
40+
defer l.mu.RUnlock()
41+
42+
if elem, ok := l.cache[key]; ok {
43+
l.lru.MoveToFront(elem)
44+
sp := trace.SpanFromContext(ctx)
45+
sp.SetAttributes(attribute.Int("cache.grid", key), attribute.Bool("cache.hit", true))
46+
return elem.Value.(*entry).value, true
47+
}
48+
49+
sp := trace.SpanFromContext(ctx)
50+
sp.SetAttributes(attribute.Int("cache.grid", key), attribute.Bool("cache.hit", false))
51+
return 0, false
52+
}
53+
54+
func (l *lruStore) Set(ctx context.Context, key int, value float32) {
55+
l.mu.Lock()
56+
defer l.mu.Unlock()
57+
58+
if elem, ok := l.cache[key]; ok {
59+
l.lru.MoveToFront(elem)
60+
elem.Value.(*entry).value = value
61+
return
62+
}
63+
64+
// 如果缓存已满,删除最久未使用的条目
65+
if l.lru.Len() >= l.capacity {
66+
oldest := l.lru.Back()
67+
if oldest != nil {
68+
delete(l.cache, oldest.Value.(*entry).key)
69+
l.lru.Remove(oldest)
70+
}
71+
}
72+
73+
// 添加新条目
74+
elem := l.lru.PushFront(&entry{key: key, value: value})
75+
l.cache[key] = elem
76+
}
77+
78+
// 简单的 map 实现
1679
type mapStore struct {
1780
mu sync.RWMutex
1881
cache map[int]float32

pkg/grib2/cache/store_test.go

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package cache
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
)
9+
10+
func BenchmarkStore_Parallel(b *testing.B) {
11+
b.Run("LRU", func(b *testing.B) {
12+
store := NewLRUStore(1000)
13+
ctx := context.Background()
14+
15+
b.RunParallel(func(pb *testing.PB) {
16+
for pb.Next() {
17+
store.Set(ctx, 1, 1.0)
18+
_, _ = store.Get(ctx, 1)
19+
}
20+
})
21+
})
22+
23+
b.Run("Map", func(b *testing.B) {
24+
store := NewMapStore()
25+
ctx := context.Background()
26+
27+
b.RunParallel(func(pb *testing.PB) {
28+
for pb.Next() {
29+
store.Set(ctx, 1, 1.0)
30+
_, _ = store.Get(ctx, 1)
31+
}
32+
})
33+
})
34+
}
35+
36+
func TestLRUStore(t *testing.T) {
37+
store := NewLRUStore(1)
38+
ctx := context.Background()
39+
40+
store.Set(ctx, 1, 1.0)
41+
v, ok := store.Get(ctx, 1)
42+
assert.True(t, ok)
43+
assert.Equal(t, float32(1.0), v)
44+
45+
store.Set(ctx, 2, 2.0)
46+
v, ok = store.Get(ctx, 2)
47+
assert.True(t, ok)
48+
assert.Equal(t, float32(2.0), v)
49+
50+
v, ok = store.Get(ctx, 3)
51+
assert.False(t, ok)
52+
assert.Equal(t, float32(0.0), v)
53+
}

0 commit comments

Comments
 (0)