Skip to content

Commit d585e85

Browse files
authored
Merge pull request #17 from lxzan/swiss
v1.2.0
2 parents 1cb66a3 + fad7777 commit d585e85

21 files changed

+849
-317
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
.idea/
22
.vscode/
33
vendor/
4-
examples/
4+
cmd/
55
bin/
66
go.work*
77
.DS_Store

README.md

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ Minimalist in-memory KV storage, powered by `HashMap` and `Minimal Quad Heap`, w
2727

2828
- Storage Data Limit: Limited by maximum capacity
2929
- Expiration Time: Supported
30-
- Cache Elimination Policy: LRU-Like
30+
- Cache Eviction Policy: LRU
3131
- GC Optimization: None
3232
- Persistent: None
3333
- Locking Mechanism: Slicing + Mutual Exclusion Locking
@@ -62,23 +62,25 @@ import (
6262
)
6363

6464
func main() {
65-
mc := memorycache.New(
66-
memorycache.WithBucketNum(128), // Bucket number, recommended to be a prime number.
67-
memorycache.WithBucketSize(1000, 10000), // Bucket size, initial size and maximum capacity.
65+
mc := memorycache.New[string, any](
66+
memorycache.WithBucketNum(128), // Bucket number, recommended to be a prime number.
67+
memorycache.WithBucketSize(1000, 10000), // Bucket size, initial size and maximum capacity.
6868
memorycache.WithInterval(5*time.Second, 30*time.Second), // Active cycle cleanup interval and expiration time.
6969
)
70-
defer mc.Stop()
7170

72-
mc.Set("xxx", 1, 10*time.Second)
71+
mc.SetWithCallback("xxx", 1, time.Second, func(element *memorycache.Element[string, any], reason memorycache.Reason) {
72+
fmt.Printf("callback: key=%s, reason=%v\n", element.Key, reason)
73+
})
7374

7475
val, exist := mc.Get("xxx")
7576
fmt.Printf("val=%v, exist=%v\n", val, exist)
7677

77-
time.Sleep(32 * time.Second)
78+
time.Sleep(2 * time.Second)
7879

7980
val, exist = mc.Get("xxx")
8081
fmt.Printf("val=%v, exist=%v\n", val, exist)
8182
}
83+
8284
```
8385

8486
### Benchmark
@@ -91,12 +93,15 @@ goos: linux
9193
goarch: amd64
9294
pkg: github.com/lxzan/memorycache/benchmark
9395
cpu: AMD Ryzen 5 PRO 4650G with Radeon Graphics
94-
BenchmarkMemoryCache_Set-12 22848898 62.83 ns/op 8 B/op 0 allocs/op
95-
BenchmarkMemoryCache_Get-12 47904933 30.94 ns/op 0 B/op 0 allocs/op
96-
BenchmarkMemoryCache_SetAndGet-12 48951848 34.41 ns/op 0 B/op 0 allocs/op
97-
BenchmarkRistretto_Set-12 12992732 139.3 ns/op 118 B/op 2 allocs/op
98-
BenchmarkRistretto_Get-12 27832851 45.11 ns/op 16 B/op 1 allocs/op
99-
BenchmarkRistretto_SetAndGet-12 12232522 102.9 ns/op 32 B/op 1 allocs/op
96+
BenchmarkMemoryCache_Set-12 18891738 109.5 ns/op 11 B/op 0 allocs/op
97+
BenchmarkMemoryCache_Get-12 21813127 48.21 ns/op 0 B/op 0 allocs/op
98+
BenchmarkMemoryCache_SetAndGet-12 22530026 52.14 ns/op 0 B/op 0 allocs/op
99+
BenchmarkRistretto_Set-12 13786928 140.6 ns/op 116 B/op 2 allocs/op
100+
BenchmarkRistretto_Get-12 26299240 45.87 ns/op 16 B/op 1 allocs/op
101+
BenchmarkRistretto_SetAndGet-12 11360748 103.0 ns/op 27 B/op 1 allocs/op
102+
BenchmarkTheine_Set-12 3527848 358.2 ns/op 19 B/op 0 allocs/op
103+
BenchmarkTheine_Get-12 23234760 49.37 ns/op 0 B/op 0 allocs/op
104+
BenchmarkTheine_SetAndGet-12 6755134 176.3 ns/op 0 B/op 0 allocs/op
100105
PASS
101-
ok github.com/lxzan/memorycache/benchmark 31.772s
106+
ok github.com/lxzan/memorycache/benchmark 65.498s
102107
```

README_CN.md

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,14 @@
1717

1818
**缓存淘汰策略:**
1919

20-
1. Set 方法用于清理溢出的键值对
20+
1. Set 方法清理溢出的键值对
2121
2. 周期清理过期的键值对
2222

2323
### 原则:
2424

2525
1. 存储数据限制:受最大容量限制
2626
2. 过期时间:支持
27-
3. 缓存淘汰策略:类似 LRU
27+
3. 缓存驱逐策略:LRU
2828
4. GC 优化:无
2929
5. 持久化:无
3030
6. 锁定机制:分片和互斥锁
@@ -59,23 +59,25 @@ import (
5959
)
6060

6161
func main() {
62-
mc := memorycache.New(
63-
memorycache.WithBucketNum(128), // Bucket number, recommended to be a prime number.
64-
memorycache.WithBucketSize(1000, 10000), // Bucket size, initial size and maximum capacity.
62+
mc := memorycache.New[string, any](
63+
memorycache.WithBucketNum(128), // Bucket number, recommended to be a prime number.
64+
memorycache.WithBucketSize(1000, 10000), // Bucket size, initial size and maximum capacity.
6565
memorycache.WithInterval(5*time.Second, 30*time.Second), // Active cycle cleanup interval and expiration time.
6666
)
67-
defer mc.Stop()
6867

69-
mc.Set("xxx", 1, 10*time.Second)
68+
mc.SetWithCallback("xxx", 1, time.Second, func(element *memorycache.Element[string, any], reason memorycache.Reason) {
69+
fmt.Printf("callback: key=%s, reason=%v\n", element.Key, reason)
70+
})
7071

7172
val, exist := mc.Get("xxx")
7273
fmt.Printf("val=%v, exist=%v\n", val, exist)
7374

74-
time.Sleep(32 * time.Second)
75+
time.Sleep(2 * time.Second)
7576

7677
val, exist = mc.Get("xxx")
7778
fmt.Printf("val=%v, exist=%v\n", val, exist)
7879
}
80+
7981
```
8082

8183
### 基准测试
@@ -88,12 +90,15 @@ goos: linux
8890
goarch: amd64
8991
pkg: github.com/lxzan/memorycache/benchmark
9092
cpu: AMD Ryzen 5 PRO 4650G with Radeon Graphics
91-
BenchmarkMemoryCache_Set-12 22848898 62.83 ns/op 8 B/op 0 allocs/op
92-
BenchmarkMemoryCache_Get-12 47904933 30.94 ns/op 0 B/op 0 allocs/op
93-
BenchmarkMemoryCache_SetAndGet-12 48951848 34.41 ns/op 0 B/op 0 allocs/op
94-
BenchmarkRistretto_Set-12 12992732 139.3 ns/op 118 B/op 2 allocs/op
95-
BenchmarkRistretto_Get-12 27832851 45.11 ns/op 16 B/op 1 allocs/op
96-
BenchmarkRistretto_SetAndGet-12 12232522 102.9 ns/op 32 B/op 1 allocs/op
93+
BenchmarkMemoryCache_Set-12 18891738 109.5 ns/op 11 B/op 0 allocs/op
94+
BenchmarkMemoryCache_Get-12 21813127 48.21 ns/op 0 B/op 0 allocs/op
95+
BenchmarkMemoryCache_SetAndGet-12 22530026 52.14 ns/op 0 B/op 0 allocs/op
96+
BenchmarkRistretto_Set-12 13786928 140.6 ns/op 116 B/op 2 allocs/op
97+
BenchmarkRistretto_Get-12 26299240 45.87 ns/op 16 B/op 1 allocs/op
98+
BenchmarkRistretto_SetAndGet-12 11360748 103.0 ns/op 27 B/op 1 allocs/op
99+
BenchmarkTheine_Set-12 3527848 358.2 ns/op 19 B/op 0 allocs/op
100+
BenchmarkTheine_Get-12 23234760 49.37 ns/op 0 B/op 0 allocs/op
101+
BenchmarkTheine_SetAndGet-12 6755134 176.3 ns/op 0 B/op 0 allocs/op
97102
PASS
98-
ok github.com/lxzan/memorycache/benchmark 31.772s
103+
ok github.com/lxzan/memorycache/benchmark 65.498s
99104
```

benchmark/benchmark_test.go

Lines changed: 50 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -4,42 +4,51 @@ import (
44
"testing"
55
"time"
66

7+
"github.com/Yiling-J/theine-go"
78
"github.com/dgraph-io/ristretto"
89
"github.com/lxzan/memorycache"
910
"github.com/lxzan/memorycache/internal/utils"
10-
"github.com/maypok86/otter"
1111
)
1212

13-
const benchcount = 1000000
13+
const (
14+
sharding = 128
15+
benchcount = 1 << 20
16+
)
17+
18+
var (
19+
benchkeys = make([]string, 0, benchcount)
1420

15-
var benchkeys = make([]string, 0, benchcount)
21+
options = []memorycache.Option{
22+
memorycache.WithBucketNum(sharding),
23+
memorycache.WithBucketSize(benchcount/sharding/10, benchcount/sharding),
24+
memorycache.WithSwissTable(),
25+
}
26+
)
1627

1728
func init() {
1829
for i := 0; i < benchcount; i++ {
1930
benchkeys = append(benchkeys, string(utils.AlphabetNumeric.Generate(16)))
2031
}
2132
}
2233

34+
func getIndex(i int) int {
35+
return i & (len(benchkeys) - 1)
36+
}
37+
2338
func BenchmarkMemoryCache_Set(b *testing.B) {
24-
var mc = memorycache.New(
25-
memorycache.WithBucketNum(128),
26-
memorycache.WithBucketSize(1000, 10000),
27-
)
39+
var mc = memorycache.New[string, int](options...)
2840
b.RunParallel(func(pb *testing.PB) {
2941
var i = 0
3042
for pb.Next() {
31-
index := i % benchcount
43+
index := getIndex(i)
3244
i++
3345
mc.Set(benchkeys[index], 1, time.Hour)
3446
}
3547
})
3648
}
3749

3850
func BenchmarkMemoryCache_Get(b *testing.B) {
39-
var mc = memorycache.New(
40-
memorycache.WithBucketNum(128),
41-
memorycache.WithBucketSize(1000, 10000),
42-
)
51+
var mc = memorycache.New[string, int](options...)
4352
for i := 0; i < benchcount; i++ {
4453
mc.Set(benchkeys[i%benchcount], 1, time.Hour)
4554
}
@@ -48,18 +57,15 @@ func BenchmarkMemoryCache_Get(b *testing.B) {
4857
b.RunParallel(func(pb *testing.PB) {
4958
var i = 0
5059
for pb.Next() {
51-
index := i % benchcount
60+
index := getIndex(i)
5261
i++
5362
mc.Get(benchkeys[index])
5463
}
5564
})
5665
}
5766

5867
func BenchmarkMemoryCache_SetAndGet(b *testing.B) {
59-
var mc = memorycache.New(
60-
memorycache.WithBucketNum(128),
61-
memorycache.WithBucketSize(1000, 10000),
62-
)
68+
var mc = memorycache.New[string, int](options...)
6369
for i := 0; i < benchcount; i++ {
6470
mc.Set(benchkeys[i%benchcount], 1, time.Hour)
6571
}
@@ -68,7 +74,7 @@ func BenchmarkMemoryCache_SetAndGet(b *testing.B) {
6874
b.RunParallel(func(pb *testing.PB) {
6975
var i = 0
7076
for pb.Next() {
71-
index := i % benchcount
77+
index := getIndex(i)
7278
i++
7379
if index&7 == 0 {
7480
mc.Set(benchkeys[index], 1, time.Hour)
@@ -81,14 +87,14 @@ func BenchmarkMemoryCache_SetAndGet(b *testing.B) {
8187

8288
func BenchmarkRistretto_Set(b *testing.B) {
8389
var mc, _ = ristretto.NewCache(&ristretto.Config{
84-
NumCounters: 10000 * 128 * 10, // number of keys to track frequency of (10M).
85-
MaxCost: 1 << 30, // maximum cost of cache (1GB).
86-
BufferItems: 64, // number of keys per Get buffer.
90+
NumCounters: benchcount * 10, // number of keys to track frequency of (10M).
91+
MaxCost: 1 << 30, // maximum cost of cache (1GB).
92+
BufferItems: 64, // number of keys per Get buffer.
8793
})
8894
b.RunParallel(func(pb *testing.PB) {
8995
var i = 0
9096
for pb.Next() {
91-
index := i % benchcount
97+
index := getIndex(i)
9298
i++
9399
mc.SetWithTTL(benchkeys[index], 1, 1, time.Hour)
94100
}
@@ -97,9 +103,9 @@ func BenchmarkRistretto_Set(b *testing.B) {
97103

98104
func BenchmarkRistretto_Get(b *testing.B) {
99105
var mc, _ = ristretto.NewCache(&ristretto.Config{
100-
NumCounters: 1e7, // number of keys to track frequency of (10M).
101-
MaxCost: 1 << 30, // maximum cost of cache (1GB).
102-
BufferItems: 64, // number of keys per Get buffer.
106+
NumCounters: benchcount * 10, // number of keys to track frequency of (10M).
107+
MaxCost: 1 << 30, // maximum cost of cache (1GB).
108+
BufferItems: 64, // number of keys per Get buffer.
103109
})
104110
for i := 0; i < benchcount; i++ {
105111
mc.SetWithTTL(benchkeys[i%benchcount], 1, 1, time.Hour)
@@ -109,7 +115,7 @@ func BenchmarkRistretto_Get(b *testing.B) {
109115
b.RunParallel(func(pb *testing.PB) {
110116
var i = 0
111117
for pb.Next() {
112-
index := i % benchcount
118+
index := getIndex(i)
113119
i++
114120
mc.Get(benchkeys[index])
115121
}
@@ -118,9 +124,9 @@ func BenchmarkRistretto_Get(b *testing.B) {
118124

119125
func BenchmarkRistretto_SetAndGet(b *testing.B) {
120126
var mc, _ = ristretto.NewCache(&ristretto.Config{
121-
NumCounters: 10000 * 128 * 10, // number of keys to track frequency of (10M).
122-
MaxCost: 1 << 30, // maximum cost of cache (1GB).
123-
BufferItems: 64, // number of keys per Get buffer.
127+
NumCounters: benchcount * 10, // number of keys to track frequency of (10M).
128+
MaxCost: 1 << 30, // maximum cost of cache (1GB).
129+
BufferItems: 64, // number of keys per Get buffer.
124130
})
125131
for i := 0; i < benchcount; i++ {
126132
mc.SetWithTTL(benchkeys[i%benchcount], 1, 1, time.Hour)
@@ -130,7 +136,7 @@ func BenchmarkRistretto_SetAndGet(b *testing.B) {
130136
b.RunParallel(func(pb *testing.PB) {
131137
var i = 0
132138
for pb.Next() {
133-
index := i % benchcount
139+
index := getIndex(i)
134140
i++
135141
if index&7 == 0 {
136142
mc.SetWithTTL(benchkeys[index], 1, 1, time.Hour)
@@ -141,53 +147,49 @@ func BenchmarkRistretto_SetAndGet(b *testing.B) {
141147
})
142148
}
143149

144-
func BenchmarkOtter_Set(b *testing.B) {
145-
var mc, _ = otter.MustBuilder[string, int](10000 * 128).Build()
150+
func BenchmarkTheine_Set(b *testing.B) {
151+
mc, _ := theine.NewBuilder[string, int](benchcount).Build()
146152
b.RunParallel(func(pb *testing.PB) {
147153
i := 0
148154
for pb.Next() {
149-
index := i % benchcount
155+
index := getIndex(i)
150156
i++
151-
mc.SetWithTTL(benchkeys[index], 1, time.Hour)
157+
mc.SetWithTTL(benchkeys[index], 1, 1, time.Hour)
152158
}
153159
})
154160
}
155161

156-
func BenchmarkOtter_Get(b *testing.B) {
157-
mc, _ := otter.MustBuilder[string, int](10000 * 128).Build()
162+
func BenchmarkTheine_Get(b *testing.B) {
163+
mc, _ := theine.NewBuilder[string, int](benchcount).Build()
158164
for i := 0; i < benchcount; i++ {
159-
mc.SetWithTTL(benchkeys[i%benchcount], 1, time.Hour)
165+
mc.SetWithTTL(benchkeys[i%benchcount], 1, 1, time.Hour)
160166
}
161167

162168
b.ResetTimer()
163169
b.RunParallel(func(pb *testing.PB) {
164170
i := 0
165171
for pb.Next() {
166-
index := i % benchcount
172+
index := getIndex(i)
167173
i++
168-
if index&7 == 0 {
169-
mc.SetWithTTL(benchkeys[index], 1, time.Hour)
170-
} else {
171-
mc.Get(benchkeys[index])
172-
}
174+
mc.Get(benchkeys[index])
173175
}
174176
})
175177
}
176178

177-
func BenchmarkOtter_SetAndGet(b *testing.B) {
178-
mc, _ := otter.MustBuilder[string, int](10000 * 128).Build()
179+
func BenchmarkTheine_SetAndGet(b *testing.B) {
180+
mc, _ := theine.NewBuilder[string, int](benchcount).Build()
179181
for i := 0; i < benchcount; i++ {
180-
mc.SetWithTTL(benchkeys[i%benchcount], 1, time.Hour)
182+
mc.SetWithTTL(benchkeys[i%benchcount], 1, 1, time.Hour)
181183
}
182184

183185
b.ResetTimer()
184186
b.RunParallel(func(pb *testing.PB) {
185187
i := 0
186188
for pb.Next() {
187-
index := i % benchcount
189+
index := getIndex(i)
188190
i++
189191
if index&7 == 0 {
190-
mc.SetWithTTL(benchkeys[index], 1, time.Hour)
192+
mc.SetWithTTL(benchkeys[index], 1, 1, time.Hour)
191193
} else {
192194
mc.Get(benchkeys[index])
193195
}

benchmark/go.mod

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ module github.com/lxzan/memorycache/benchmark
33
go 1.19
44

55
require (
6+
github.com/Yiling-J/theine-go v0.3.1
67
github.com/dgraph-io/ristretto v0.1.1
78
github.com/lxzan/memorycache v1.0.0
8-
github.com/maypok86/otter v0.0.0-20231202212625-970153160a38
99
)
1010

1111
require (
@@ -16,6 +16,7 @@ require (
1616
github.com/gammazero/deque v0.2.1 // indirect
1717
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect
1818
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
19+
github.com/ncw/directio v1.0.5 // indirect
1920
github.com/pkg/errors v0.9.1 // indirect
2021
github.com/zeebo/xxh3 v1.0.2 // indirect
2122
golang.org/x/sys v0.15.0 // indirect

0 commit comments

Comments
 (0)