Skip to content

Commit 2871dd1

Browse files
authored
Merge pull request #3 from lxzan/dev
v1.1.2
2 parents db93db0 + ce1d9f2 commit 2871dd1

File tree

6 files changed

+92
-70
lines changed

6 files changed

+92
-70
lines changed

README.md

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,7 @@
1111
[4]: https://codecov.io/gh/lxzan/memorycache
1212

1313
### Description
14-
Minimalist in-memory KV storage, powered by hashmap and minimal heap, without optimizations for GC.
15-
It has O(1) read efficiency, O(logN) write efficiency.
14+
Minimalist in-memory KV storage, powered by hashmap and minimal quad heap, without optimizations for GC.
1615
Cache deprecation policy: the set method cleans up overflowed keys; the cycle cleans up expired keys.
1716

1817
### Principle
@@ -59,10 +58,10 @@ go test -benchmem -run=^$ -bench . github.com/lxzan/memorycache/benchmark
5958
goos: darwin
6059
goarch: arm64
6160
pkg: github.com/lxzan/memorycache/benchmark
62-
BenchmarkMemoryCache_Set-8 7038808 153.9 ns/op 26 B/op 0 allocs/op
63-
BenchmarkMemoryCache_Get-8 22969712 50.92 ns/op 0 B/op 0 allocs/op
64-
BenchmarkRistretto_Set-8 13417420 242.9 ns/op 138 B/op 2 allocs/op
65-
BenchmarkRistretto_Get-8 15895714 75.81 ns/op 18 B/op 1 allocs/op
61+
BenchmarkMemoryCache_Set-8 9836241 117.0 ns/op 19 B/op 0 allocs/op
62+
BenchmarkMemoryCache_Get-8 17689178 67.77 ns/op 0 B/op 0 allocs/op
63+
BenchmarkRistretto_Set-8 14112769 256.2 ns/op 135 B/op 2 allocs/op
64+
BenchmarkRistretto_Get-8 15645778 77.72 ns/op 18 B/op 1 allocs/op
6665
PASS
67-
ok github.com/lxzan/memorycache/benchmark 10.849s
66+
ok github.com/lxzan/memorycache/benchmark 14.059s
6867
```

benchmark/benchmark_test.go

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,29 +24,29 @@ func BenchmarkMemoryCache_Set(b *testing.B) {
2424
memorycache.WithBucketNum(128),
2525
memorycache.WithBucketSize(1000, 10000),
2626
)
27-
var i = int64(0)
27+
var i = atomic.Int64{}
2828
b.RunParallel(func(pb *testing.PB) {
2929
for pb.Next() {
30-
index := atomic.AddInt64(&i, 1) % benchcount
30+
index := i.Add(1) % benchcount
3131
mc.Set(benchkeys[index], 1, time.Hour)
3232
}
3333
})
3434
}
3535

3636
func BenchmarkMemoryCache_Get(b *testing.B) {
3737
var mc = memorycache.New(
38-
memorycache.WithBucketNum(16),
39-
memorycache.WithBucketSize(100, 1000),
38+
memorycache.WithBucketNum(128),
39+
memorycache.WithBucketSize(1000, 10000),
4040
)
4141
for i := 0; i < benchcount; i++ {
4242
mc.Set(benchkeys[i%benchcount], 1, time.Hour)
4343
}
4444

45-
var i = int64(0)
45+
var i = atomic.Int64{}
4646
b.ResetTimer()
4747
b.RunParallel(func(pb *testing.PB) {
4848
for pb.Next() {
49-
index := atomic.AddInt64(&i, 1) % benchcount
49+
index := i.Add(1) % benchcount
5050
mc.Get(benchkeys[index])
5151
}
5252
})
@@ -58,10 +58,10 @@ func BenchmarkRistretto_Set(b *testing.B) {
5858
MaxCost: 1 << 30, // maximum cost of cache (1GB).
5959
BufferItems: 64, // number of keys per Get buffer.
6060
})
61-
var i = int64(0)
61+
var i = atomic.Int64{}
6262
b.RunParallel(func(pb *testing.PB) {
6363
for pb.Next() {
64-
index := atomic.AddInt64(&i, 1) % benchcount
64+
index := i.Add(1) % benchcount
6565
mc.SetWithTTL(benchkeys[index], 1, 1, time.Hour)
6666
}
6767
})
@@ -77,11 +77,11 @@ func BenchmarkRistretto_Get(b *testing.B) {
7777
mc.SetWithTTL(benchkeys[i%benchcount], 1, 1, time.Hour)
7878
}
7979

80-
var i = int64(0)
80+
var i = atomic.Int64{}
8181
b.ResetTimer()
8282
b.RunParallel(func(pb *testing.PB) {
8383
for pb.Next() {
84-
index := atomic.AddInt64(&i, 1) % benchcount
84+
index := i.Add(1) % benchcount
8585
mc.Get(benchkeys[index])
8686
}
8787
})

index.go

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,17 @@ func (c *MemoryCache) getExp(d time.Duration) int64 {
6767
return time.Now().Add(d).UnixMilli()
6868
}
6969

70+
// Clear 清空所有缓存
71+
// clear all caches
72+
func (c *MemoryCache) Clear() {
73+
for _, b := range c.storage {
74+
b.Lock()
75+
b.Heap = heap.New(c.config.InitialSize)
76+
b.Map = make(map[string]*types.Element, c.config.InitialSize)
77+
b.Unlock()
78+
}
79+
}
80+
7081
// Set 设置键值和过期时间. exp<=0表示永不过期.
7182
// Set the key value and expiration time. exp<=0 means never expire.
7283
func (c *MemoryCache) Set(key string, value any, exp time.Duration) (replaced bool) {
@@ -137,15 +148,15 @@ func (c *MemoryCache) Delete(key string) (deleted bool) {
137148
return true
138149
}
139150

140-
// Keys 获取前缀匹配的key. 可以通过星号获取所有的key.
141-
// Get prefix matching key, You can get all the keys with an asterisk.
151+
// Keys 获取前缀匹配的key
152+
// Get prefix matching key
142153
func (c *MemoryCache) Keys(prefix string) []string {
143154
var arr = make([]string, 0)
144155
var now = time.Now().UnixMilli()
145156
for _, b := range c.storage {
146157
b.Lock()
147158
for _, v := range b.Heap.Data {
148-
if !v.Expired(now) && (prefix == "*" || strings.HasPrefix(v.Key, prefix)) {
159+
if !v.Expired(now) && strings.HasPrefix(v.Key, prefix) {
149160
arr = append(arr, v.Key)
150161
}
151162
}
@@ -154,23 +165,13 @@ func (c *MemoryCache) Keys(prefix string) []string {
154165
return arr
155166
}
156167

157-
// Len 获取元素数量
168+
// Len 获取当前元素数量
158169
// Get the number of elements
159-
// @check: 是否检查过期时间 (whether to check expiration time)
160-
func (c *MemoryCache) Len(check bool) int {
170+
func (c *MemoryCache) Len() int {
161171
var num = 0
162-
var now = time.Now().UnixMilli()
163172
for _, b := range c.storage {
164173
b.Lock()
165-
if !check {
166-
num += b.Heap.Len()
167-
} else {
168-
for _, v := range b.Heap.Data {
169-
if !v.Expired(now) {
170-
num++
171-
}
172-
}
173-
}
174+
num += b.Heap.Len()
174175
b.Unlock()
175176
}
176177
return num

index_test.go

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ func TestNew(t *testing.T) {
2121
db.Set("c", 1, time.Millisecond)
2222

2323
time.Sleep(20 * time.Millisecond)
24-
as.ElementsMatch(db.Keys("*"), []string{"b", "d", "e"})
24+
as.ElementsMatch(db.Keys(""), []string{"b", "d", "e"})
2525
})
2626

2727
t.Run("", func(t *testing.T) {
@@ -34,7 +34,7 @@ func TestNew(t *testing.T) {
3434
db.Set("a", 1, 40*time.Millisecond)
3535

3636
time.Sleep(30 * time.Millisecond)
37-
as.ElementsMatch(db.Keys("*"), []string{"a", "c", "d", "e"})
37+
as.ElementsMatch(db.Keys(""), []string{"a", "c", "d", "e"})
3838
})
3939

4040
t.Run("", func(t *testing.T) {
@@ -46,14 +46,15 @@ func TestNew(t *testing.T) {
4646
db.Set("d", 1, 40*time.Millisecond)
4747

4848
time.Sleep(50 * time.Millisecond)
49-
as.Equal(0, db.Len(true))
49+
as.Equal(0, len(db.Keys("")))
5050
})
5151
}
5252

5353
func TestMemoryCache_Set(t *testing.T) {
5454
var list []string
5555
var count = 10000
5656
var mc = New(WithInterval(100 * time.Millisecond))
57+
mc.Clear()
5758
for i := 0; i < count; i++ {
5859
key := string(utils.AlphabetNumeric.Generate(8))
5960
exp := rand.Intn(1000)
@@ -69,7 +70,7 @@ func TestMemoryCache_Set(t *testing.T) {
6970
mc.Set(key, 1, time.Duration(exp)*time.Millisecond)
7071
}
7172
time.Sleep(1100 * time.Millisecond)
72-
assert.ElementsMatch(t, utils.Uniq(list), mc.Keys("*"))
73+
assert.ElementsMatch(t, utils.Uniq(list), mc.Keys(""))
7374
}
7475

7576
func TestMemoryCache_Get(t *testing.T) {
@@ -115,7 +116,7 @@ func TestMemoryCache_GetAndRefresh(t *testing.T) {
115116
list = append(list, key)
116117
mc.Set(key, 1, time.Duration(exp)*time.Millisecond)
117118
}
118-
var keys = mc.Keys("*")
119+
var keys = mc.Keys("")
119120
for _, key := range keys {
120121
mc.GetAndRefresh(key, 2*time.Second)
121122
}
@@ -141,7 +142,7 @@ func TestMemoryCache_Delete(t *testing.T) {
141142
mc.Set(key, 1, time.Duration(exp)*time.Millisecond)
142143
}
143144

144-
var keys = mc.Keys("*")
145+
var keys = mc.Keys("")
145146
for i := 0; i < 100; i++ {
146147
deleted := mc.Delete(keys[i])
147148
assert.True(t, deleted)
@@ -150,7 +151,7 @@ func TestMemoryCache_Delete(t *testing.T) {
150151
deleted = mc.Delete(key)
151152
assert.False(t, deleted)
152153
}
153-
assert.Equal(t, mc.Len(true), count-100)
154+
assert.Equal(t, mc.Len(), count-100)
154155
}
155156

156157
func TestMaxCap(t *testing.T) {
@@ -164,5 +165,5 @@ func TestMaxCap(t *testing.T) {
164165
mc.Set(key, 1, -1)
165166
}
166167
time.Sleep(200 * time.Millisecond)
167-
assert.Equal(t, mc.Len(false), 100)
168+
assert.Equal(t, mc.Len(), 100)
168169
}

internal/heap/heap.go

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@ type Heap struct {
1414

1515
func (c *Heap) Less(i, j int) bool { return c.Data[i].ExpireAt < c.Data[j].ExpireAt }
1616

17+
func (c *Heap) min(i, j int) int {
18+
if c.Data[i].ExpireAt < c.Data[j].ExpireAt {
19+
return i
20+
}
21+
return j
22+
}
23+
1724
func (c *Heap) Len() int {
1825
return len(c.Data)
1926
}
@@ -30,10 +37,12 @@ func (c *Heap) Push(ele *types.Element) {
3037
}
3138

3239
func (c *Heap) Up(i int) {
33-
var j = (i - 1) / 2
34-
if j >= 0 && c.Less(i, j) {
35-
c.Swap(i, j)
36-
c.Up(j)
40+
if i > 0 {
41+
var j = (i - 1) >> 2
42+
if c.Less(i, j) {
43+
c.Swap(i, j)
44+
c.Up(j)
45+
}
3746
}
3847
}
3948

@@ -61,18 +70,27 @@ func (c *Heap) Delete(i int) {
6170
}
6271

6372
func (c *Heap) Down(i, n int) {
64-
var j = 2*i + 1
65-
var k = 2*i + 2
66-
var x = -1
67-
if j < n {
68-
x = j
69-
}
70-
if k < n && c.Less(k, j) {
71-
x = k
73+
var j = -1
74+
var index1 = i<<2 + 1
75+
var index2 = i<<2 + 2
76+
var index3 = i<<2 + 3
77+
var index4 = i<<2 + 4
78+
79+
if index1 >= n {
80+
return
81+
} else if index4 < n {
82+
j = c.min(c.min(index1, index2), c.min(index3, index4))
83+
} else if index3 < n {
84+
j = c.min(c.min(index1, index2), index3)
85+
} else if index2 < n {
86+
j = c.min(index1, index2)
87+
} else {
88+
j = index1
7289
}
73-
if x != -1 && c.Less(x, i) {
74-
c.Swap(i, x)
75-
c.Down(x, n)
90+
91+
if j >= 0 && c.Less(j, i) {
92+
c.Swap(i, j)
93+
c.Down(j, n)
7694
}
7795
}
7896

internal/heap/heap_test.go

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,29 +3,32 @@ package heap
33
import (
44
"github.com/lxzan/memorycache/internal/types"
55
"github.com/stretchr/testify/assert"
6+
"math/rand"
7+
"sort"
68
"testing"
79
)
810

911
func TestHeap_Sort(t *testing.T) {
1012
var as = assert.New(t)
1113
var h = New(0)
12-
h.Push(&types.Element{ExpireAt: 1})
13-
h.Push(&types.Element{ExpireAt: 3})
14-
h.Push(&types.Element{ExpireAt: 5})
15-
h.Push(&types.Element{ExpireAt: 7})
16-
h.Push(&types.Element{ExpireAt: 9})
17-
h.Push(&types.Element{ExpireAt: 2})
18-
h.Push(&types.Element{ExpireAt: 4})
19-
h.Push(&types.Element{ExpireAt: 6})
20-
h.Push(&types.Element{ExpireAt: 8})
21-
h.Push(&types.Element{ExpireAt: 10})
14+
for i := 0; i < 1000; i++ {
15+
num := rand.Int63n(1000)
16+
h.Push(&types.Element{ExpireAt: num})
17+
}
18+
19+
as.LessOrEqual(h.Front().ExpireAt, h.Data[1].ExpireAt)
20+
as.LessOrEqual(h.Front().ExpireAt, h.Data[2].ExpireAt)
21+
as.LessOrEqual(h.Front().ExpireAt, h.Data[3].ExpireAt)
22+
as.LessOrEqual(h.Front().ExpireAt, h.Data[4].ExpireAt)
2223

23-
as.Equal(h.Front().ExpireAt, int64(1))
24-
var listA = make([]int64, 0)
24+
var list = make([]int64, 0)
2525
for h.Len() > 0 {
26-
listA = append(listA, h.Pop().ExpireAt)
26+
list = append(list, h.Pop().ExpireAt)
2727
}
28-
as.ElementsMatch(listA, []int64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
28+
ok := sort.SliceIsSorted(list, func(i, j int) bool {
29+
return list[i] < list[j]
30+
})
31+
as.True(ok)
2932
as.Nil(h.Pop())
3033
}
3134

0 commit comments

Comments
 (0)