Skip to content

Commit 48c1abc

Browse files
authored
gc optimize (#20)
gc optimize
1 parent 16f20c6 commit 48c1abc

25 files changed

+782
-429
lines changed

.editorconfig

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# EditorConfig is awesome: https://EditorConfig.org
2+
3+
# top-most EditorConfig file
4+
root = true
5+
6+
[*]
7+
end_of_line = lf
8+
insert_final_newline = true
9+
10+
[*.go]
11+
indent_style = tab
12+
indent_size = 4
13+
14+
[Makefile]
15+
indent_style = tab
16+
17+
[*.{yml,yaml}]
18+
indent_style = space
19+
indent_size = 2

.github/workflows/go.yml

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,15 @@ jobs:
1414
build:
1515
runs-on: ubuntu-latest
1616
steps:
17-
- uses: actions/checkout@v3
17+
- uses: actions/checkout@v3
1818

19-
- name: Set up Go
20-
uses: actions/setup-go@v3
21-
with:
22-
go-version: 1.19
19+
- name: Set up Go
20+
uses: actions/setup-go@v3
21+
with:
22+
go-version: 1.19
2323

24-
- name: Test
25-
run: go test -v ./...
26-
27-
- name: Bench
28-
run: go work init && go work use benchmark && make bench
24+
- name: Test
25+
run: make test
26+
27+
- name: Bench
28+
run: make bench

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@
33
vendor/
44
cmd/
55
bin/
6-
go.work*
6+
examples/
77
.DS_Store

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
test:
22
go test -count 1 -timeout 30s -run ^Test ./...
3+
go test -count 1 -timeout 30s -run ^Test github.com/lxzan/memorycache/benchmark
34

45
bench:
56
go test -benchmem -run=^$$ -bench . github.com/lxzan/memorycache/benchmark

README.md

Lines changed: 36 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -10,45 +10,52 @@
1010
[![Build Status][1]][2] [![codecov][3]][4]
1111

1212
[1]: https://github.com/lxzan/memorycache/workflows/Go%20Test/badge.svg?branch=main
13+
1314
[2]: https://github.com/lxzan/memorycache/actions?query=branch%3Amain
15+
1416
[3]: https://codecov.io/gh/lxzan/memorycache/graph/badge.svg?token=OHD6918OPT
17+
1518
[4]: https://codecov.io/gh/lxzan/memorycache
1619

1720
### Description
1821

19-
Minimalist in-memory KV storage, powered by `HashMap` and `Minimal Quad Heap`, without optimizations for GC.
22+
Minimalist in-memory KV storage, powered by `HashMap` and `Minimal Quad Heap`.
2023

2124
**Cache Elimination Policy:**
2225

23-
1. Set method cleans up overflowed keys
24-
2. Active cycle cleans up expired keys
26+
- Set method cleans up overflowed keys
27+
- Active cycle cleans up expired keys
2528

2629
### Principle
2730

28-
- Storage Data Limit: Limited by maximum capacity
29-
- Expiration Time: Supported
30-
- Cache Eviction Policy: LRU
31-
- GC Optimization: None
32-
- Persistent: None
33-
- Locking Mechanism: Slicing + Mutual Exclusion Locking
31+
- Storage Data Limit: Limited by maximum capacity
32+
- Expiration Time: Supported
33+
- Cache Eviction Policy: LRU
34+
- Persistent: None
35+
- Locking Mechanism: Slicing + Mutual Exclusion Locking
36+
- HashMap, Heap and LinkedList (excluding user KVs) implemented in pointerless technology
3437

3538
### Advantage
3639

37-
- Simple and easy to use
38-
- No third-party dependencies
39-
- High performance
40-
- Low memory usage
41-
- Use quadruple heap to maintain the expiration time, effectively reduce the height of the tree, and improve the insertion performance
40+
- Simple and easy to use
41+
- High performance
42+
- Low memory usage
43+
- Use quadruple heap to maintain the expiration time, effectively reduce the height of the tree, and improve the
44+
insertion performance
4245

4346
### Methods
4447

45-
- [x] **Set** : Set key-value pair with expiring time. If the key already exists, the value will be updated. Also the expiration time will be updated.
46-
- [x] **SetWithCallback** : Set key-value pair with expiring time and callback function. If the key already exists, the value will be updated. Also the expiration time will be updated.
48+
- [x] **Set** : Set key-value pair with expiring time. If the key already exists, the value will be updated. Also the
49+
expiration time will be updated.
50+
- [x] **SetWithCallback** : Set key-value pair with expiring time and callback function. If the key already exists,
51+
the value will be updated. Also the expiration time will be updated.
4752
- [x] **Get** : Get value by key. If the key does not exist, the second return value will be false.
48-
- [x] **GetWithTTL** : Get value by key. If the key does not exist, the second return value will be false. When return value, method will refresh the expiration time.
53+
- [x] **GetWithTTL** : Get value by key. If the key does not exist, the second return value will be false. When return
54+
value, method will refresh the expiration time.
4955
- [x] **Delete** : Delete key-value pair by key.
5056
- [x] **GetOrCreate** : Get value by key. If the key does not exist, the value will be created.
51-
- [x] **GetOrCreateWithCallback** : Get value by key. If the key does not exist, the value will be created. Also the callback function will be called.
57+
- [x] **GetOrCreateWithCallback** : Get value by key. If the key does not exist, the value will be created. Also the
58+
callback function will be called.
5259

5360
### Example
5461

@@ -85,23 +92,22 @@ func main() {
8592

8693
### Benchmark
8794

88-
- 1,000,000 elements
95+
- 1,000,000 elements
8996

9097
```
91-
go test -benchmem -run=^$ -bench . github.com/lxzan/memorycache/benchmark
9298
goos: linux
9399
goarch: amd64
94100
pkg: github.com/lxzan/memorycache/benchmark
95101
cpu: AMD Ryzen 5 PRO 4650G with Radeon Graphics
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
102+
BenchmarkMemoryCache_Set-8 16107153 74.85 ns/op 15 B/op 0 allocs/op
103+
BenchmarkMemoryCache_Get-8 28859542 42.34 ns/op 0 B/op 0 allocs/op
104+
BenchmarkMemoryCache_SetAndGet-8 27317874 63.02 ns/op 0 B/op 0 allocs/op
105+
BenchmarkRistretto_Set-8 13343023 272.6 ns/op 120 B/op 2 allocs/op
106+
BenchmarkRistretto_Get-8 19799044 55.06 ns/op 17 B/op 1 allocs/op
107+
BenchmarkRistretto_SetAndGet-8 11212923 119.6 ns/op 30 B/op 1 allocs/op
108+
BenchmarkTheine_Set-8 3775975 322.5 ns/op 30 B/op 0 allocs/op
109+
BenchmarkTheine_Get-8 21579301 54.94 ns/op 0 B/op 0 allocs/op
110+
BenchmarkTheine_SetAndGet-8 6265330 224.6 ns/op 0 B/op 0 allocs/op
105111
PASS
106-
ok github.com/lxzan/memorycache/benchmark 65.498s
112+
ok github.com/lxzan/memorycache/benchmark 53.498s
107113
```

README_CN.md

Lines changed: 27 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,35 +7,37 @@
77
[![Build Status][1]][2] [![codecov][3]][4]
88

99
[1]: https://github.com/lxzan/memorycache/workflows/Go%20Test/badge.svg?branch=main
10+
1011
[2]: https://github.com/lxzan/memorycache/actions?query=branch%3Amain
12+
1113
[3]: https://codecov.io/gh/lxzan/memorycache/graph/badge.svg?token=OHD6918OPT
14+
1215
[4]: https://codecov.io/gh/lxzan/memorycache
1316

1417
### 简介:
1518

16-
极简的内存键值(KV)存储系统,其核心由哈希表(HashMap) 和最小四叉堆(Minimal Quad Heap) 构成,没有进行垃圾回收(GC)优化。
19+
极简的内存键值(KV)存储系统,其核心由哈希表(HashMap) 和最小四叉堆(Minimal Quad Heap) 构成.
1720

1821
**缓存淘汰策略:**
1922

20-
1. Set 方法清理溢出的键值对
21-
2. 周期清理过期的键值对
23+
- Set 方法清理溢出的键值对
24+
- 周期清理过期的键值对
2225

2326
### 原则:
2427

25-
1. 存储数据限制:受最大容量限制
26-
2. 过期时间:支持
27-
3. 缓存驱逐策略:LRU
28-
4. GC 优化:无
29-
5. 持久化:无
30-
6. 锁定机制:分片和互斥锁
28+
- 存储数据限制:受最大容量限制
29+
- 过期时间:支持
30+
- 缓存驱逐策略:LRU
31+
- 持久化:无
32+
- 锁定机制:分片和互斥锁
33+
- GC 优化:无指针技术实现的哈希表, 最小堆和链表(不包括用户KV)
3134

3235
### 优势:
3336

34-
1. 简单易用
35-
2. 无需第三方依赖
36-
3. 高性能
37-
4. 内存占用低
38-
5. 使用四叉堆维护过期时间, 有效降低树高度, 提高插入性能
37+
- 简单易用
38+
- 高性能
39+
- 内存占用低
40+
- 使用四叉堆维护过期时间, 有效降低树高度, 提高插入性能
3941

4042
### 方法:
4143

@@ -82,23 +84,22 @@ func main() {
8284

8385
### 基准测试
8486

85-
- 1,000,000 元素
87+
- 1,000,000 元素
8688

8789
```
88-
go test -benchmem -run=^$ -bench . github.com/lxzan/memorycache/benchmark
8990
goos: linux
9091
goarch: amd64
9192
pkg: github.com/lxzan/memorycache/benchmark
9293
cpu: AMD Ryzen 5 PRO 4650G with Radeon Graphics
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
94+
BenchmarkMemoryCache_Set-8 16107153 74.85 ns/op 15 B/op 0 allocs/op
95+
BenchmarkMemoryCache_Get-8 28859542 42.34 ns/op 0 B/op 0 allocs/op
96+
BenchmarkMemoryCache_SetAndGet-8 27317874 63.02 ns/op 0 B/op 0 allocs/op
97+
BenchmarkRistretto_Set-8 13343023 272.6 ns/op 120 B/op 2 allocs/op
98+
BenchmarkRistretto_Get-8 19799044 55.06 ns/op 17 B/op 1 allocs/op
99+
BenchmarkRistretto_SetAndGet-8 11212923 119.6 ns/op 30 B/op 1 allocs/op
100+
BenchmarkTheine_Set-8 3775975 322.5 ns/op 30 B/op 0 allocs/op
101+
BenchmarkTheine_Get-8 21579301 54.94 ns/op 0 B/op 0 allocs/op
102+
BenchmarkTheine_SetAndGet-8 6265330 224.6 ns/op 0 B/op 0 allocs/op
102103
PASS
103-
ok github.com/lxzan/memorycache/benchmark 65.498s
104+
ok github.com/lxzan/memorycache/benchmark 53.498s
104105
```

benchmark/benchmark_test.go

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ import (
44
"testing"
55
"time"
66

7+
"github.com/stretchr/testify/assert"
8+
9+
lru "github.com/hashicorp/golang-lru/v2"
10+
711
"github.com/Yiling-J/theine-go"
812
"github.com/dgraph-io/ristretto"
913
"github.com/lxzan/memorycache"
@@ -22,8 +26,7 @@ var (
2226
options = []memorycache.Option{
2327
memorycache.WithBucketNum(sharding),
2428
memorycache.WithBucketSize(capacity/10, capacity),
25-
memorycache.WithSwissTable(true),
26-
memorycache.WithLRU(true),
29+
memorycache.WithSwissTable(false),
2730
}
2831
)
2932

@@ -198,3 +201,38 @@ func BenchmarkTheine_SetAndGet(b *testing.B) {
198201
}
199202
})
200203
}
204+
205+
// 测试LRU算法实现的正确性
206+
func TestLRU_Impl(t *testing.T) {
207+
var f = func() {
208+
var count = 10000
209+
var capacity = 5000
210+
var mc = memorycache.New[string, int](
211+
memorycache.WithBucketNum(1),
212+
memorycache.WithBucketSize(capacity, capacity),
213+
)
214+
var cache, _ = lru.New[string, int](capacity)
215+
for i := 0; i < count; i++ {
216+
key := string(utils.AlphabetNumeric.Generate(16))
217+
val := utils.AlphabetNumeric.Intn(capacity)
218+
mc.Set(key, val, time.Hour)
219+
cache.Add(key, val)
220+
}
221+
222+
keys := cache.Keys()
223+
assert.Equal(t, mc.Len(), capacity)
224+
assert.Equal(t, mc.Len(), cache.Len())
225+
assert.Equal(t, mc.Len(), len(keys))
226+
227+
for _, key := range keys {
228+
v1, ok1 := mc.Get(key)
229+
v2, _ := cache.Peek(key)
230+
assert.True(t, ok1)
231+
assert.Equal(t, v1, v2)
232+
}
233+
}
234+
235+
for i := 0; i < 10; i++ {
236+
f()
237+
}
238+
}

benchmark/go.mod

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,28 @@ go 1.19
55
require (
66
github.com/Yiling-J/theine-go v0.3.1
77
github.com/dgraph-io/ristretto v0.1.1
8+
github.com/hashicorp/golang-lru/v2 v2.0.7
89
github.com/lxzan/memorycache v1.0.0
10+
github.com/stretchr/testify v1.8.4
911
)
1012

1113
require (
1214
github.com/cespare/xxhash/v2 v2.1.2 // indirect
15+
github.com/davecgh/go-spew v1.1.1 // indirect
1316
github.com/dolthub/maphash v0.1.0 // indirect
1417
github.com/dolthub/swiss v0.2.1 // indirect
1518
github.com/dustin/go-humanize v1.0.0 // indirect
1619
github.com/gammazero/deque v0.2.1 // indirect
1720
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect
1821
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
22+
github.com/kr/text v0.2.0 // indirect
23+
github.com/lxzan/dao v1.1.2 // indirect
1924
github.com/ncw/directio v1.0.5 // indirect
2025
github.com/pkg/errors v0.9.1 // indirect
26+
github.com/pmezard/go-difflib v1.0.0 // indirect
2127
github.com/zeebo/xxh3 v1.0.2 // indirect
2228
golang.org/x/sys v0.15.0 // indirect
29+
gopkg.in/yaml.v3 v3.0.1 // indirect
2330
)
2431

2532
replace github.com/lxzan/memorycache v1.0.0 => ../

benchmark/go.sum

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ github.com/Yiling-J/theine-go v0.3.1/go.mod h1:9HtlXa6gjwnqdhqW0R/0BDHxGF4CNmZdV
33
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
44
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
55
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
6+
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
67
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
78
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
89
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -20,8 +21,15 @@ github.com/gammazero/deque v0.2.1 h1:qSdsbG6pgp6nL7A0+K/B7s12mcCY/5l5SIUpMOl+dC0
2021
github.com/gammazero/deque v0.2.1/go.mod h1:LFroj8x4cMYCukHJDbxFCkT+r9AndaJnFMuZDV34tuU=
2122
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
2223
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
24+
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
25+
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
2326
github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
2427
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
28+
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
29+
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
30+
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
31+
github.com/lxzan/dao v1.1.2 h1:ATYm6yBE117iQetDTmCZeHB4mBFjO43NbsEt7wf/JBo=
32+
github.com/lxzan/dao v1.1.2/go.mod h1:5ChTIo7RSZ4upqRo16eicJ3XdJWhGwgMIsyuGLMUofM=
2533
github.com/ncw/directio v1.0.5 h1:JSUBhdjEvVaJvOoyPAbcW0fnd0tvRXD76wEfZ1KcQz4=
2634
github.com/ncw/directio v1.0.5/go.mod h1:rX/pKEYkOXBGOggmcyJeJGloCkleSvphPx2eV3t6ROk=
2735
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
@@ -30,7 +38,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
3038
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
3139
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
3240
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
33-
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
41+
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
42+
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
3443
github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
3544
github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0=
3645
github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
@@ -39,5 +48,7 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
3948
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
4049
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
4150
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
51+
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
4252
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
4353
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
54+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

0 commit comments

Comments
 (0)