Skip to content

Commit 358c863

Browse files
committed
cache: generic cache
The old cache is implemented with the new cache. The goal of this v2 cache is to improve ergonomics of working with caches The following benchmark shows the speed difference for the hash functions ``` $ go test -bench=BenchmarkHash ./cache --count 10 | benchstat -col .name - goos: linux goarch: amd64 pkg: github.com/upfluence/pkg/cache cpu: AMD Ryzen 7 PRO 7840U w/ Radeon 780M Graphics │ HashFnv64 │ HashMaphash │ │ sec/op │ sec/op vs base │ */8-16 2.178µ ± 0% 4.167µ ± 1% +91.34% (p=0.000 n=10) */16-16 3.788µ ± 1% 4.145µ ± 2% +9.42% (p=0.000 n=10) */64-16 17.119µ ± 8% 4.611µ ± 3% -73.06% (p=0.000 n=10) */256-16 52.51µ ± 4% 11.46µ ± 0% -78.18% (p=0.000 n=10) geomean 9.279µ 5.496µ -40.77% ``` It does not take into account the overhead gained from unboxing the value types, nor any possible PGO wins. An example of the keys used are domains, in per-domain shop ratelimiters.
1 parent 5ba5058 commit 358c863

20 files changed

+624
-423
lines changed

cache/cache.go

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

3-
import "io"
3+
import (
4+
"github.com/upfluence/pkg/cache/policy"
5+
"github.com/upfluence/pkg/cache/v2"
6+
)
47

5-
type Cache interface {
6-
Get(string) (interface{}, bool, error)
7-
Set(string, interface{}) error
8-
Evict(string) error
8+
type Cache cache.Cache[string, any]
99

10-
io.Closer
10+
func NewCache() Cache {
11+
return cache.NewCache[string, any]()
12+
}
13+
14+
func WithEvictionPolicy(c Cache, ep policy.EvictionPolicy) Cache {
15+
return cache.WithEvictionPolicy(c, ep)
1116
}

cache/cache_test.go

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package cache
2+
3+
import (
4+
"fmt"
5+
"hash/maphash"
6+
"math/rand/v2"
7+
"testing"
8+
)
9+
10+
const (
11+
offset64 uint64 = 14695981039346656037
12+
prime64 uint64 = 1099511628211
13+
)
14+
15+
func generateTestStrings(size, count int) []string {
16+
result := make([]string, count)
17+
18+
for i := 0; i < count; i++ {
19+
bytes := make([]byte, size)
20+
for j := range bytes {
21+
bytes[j] = byte(rand.IntN(256))
22+
}
23+
24+
result[i] = string(bytes)
25+
}
26+
27+
return result
28+
}
29+
30+
func fnv64aHash(s string) uint64 {
31+
h := offset64
32+
33+
for _, b := range []byte(s) {
34+
h ^= uint64(b)
35+
h *= prime64
36+
}
37+
38+
return h
39+
}
40+
41+
func BenchmarkHashFnv64(b *testing.B) {
42+
sizes := []int{8, 16, 64, 256}
43+
for _, size := range sizes {
44+
testData := generateTestStrings(size, 1000)
45+
46+
b.Run(fmt.Sprintf("%d", size), func(b *testing.B) {
47+
b.ResetTimer()
48+
for range b.N {
49+
for _, s := range testData {
50+
fnv64aHash(s)
51+
}
52+
}
53+
})
54+
}
55+
}
56+
57+
func BenchmarkHashMaphash(b *testing.B) {
58+
sizes := []int{8, 16, 64, 256}
59+
for _, size := range sizes {
60+
testData := generateTestStrings(size, 1000)
61+
b.Run(fmt.Sprintf("%d", size), func(b *testing.B) {
62+
b.ResetTimer()
63+
seed := maphash.MakeSeed()
64+
65+
for range b.N {
66+
for _, s := range testData {
67+
maphash.String(seed, s)
68+
}
69+
}
70+
})
71+
}
72+
}

cache/lock_cache.go

-38
This file was deleted.

cache/policy/policy.go

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package policy
2+
3+
import (
4+
policy "github.com/upfluence/pkg/cache/v2/policy"
5+
)
6+
7+
var ErrClosed = policy.ErrClosed
8+
9+
type EvictionPolicy = policy.EvictionPolicy[string]
10+
11+
func CombinePolicies(ps ...EvictionPolicy) EvictionPolicy {
12+
return policy.CombinePolicies(ps...)
13+
}
14+
15+
type NopPolicy = policy.NopPolicy[string]
16+
17+
type OpType = policy.OpType
18+
19+
const (
20+
Get = policy.Get
21+
Set = policy.Set
22+
Evict = policy.Evict
23+
)

cache/policy/size/policy.go

+4-111
Original file line numberDiff line numberDiff line change
@@ -1,118 +1,11 @@
11
package size
22

33
import (
4-
"container/list"
5-
"context"
6-
"sync"
7-
8-
"github.com/upfluence/pkg/cache/policy"
4+
"github.com/upfluence/pkg/cache/v2/policy/size"
95
)
106

11-
type Policy struct {
12-
sync.Mutex
13-
14-
size int
15-
16-
l *list.List
17-
ks map[string]*list.Element
18-
19-
ctx context.Context
20-
cancel context.CancelFunc
21-
22-
fn func(string)
23-
24-
closeOnce sync.Once
25-
ch chan string
26-
}
27-
28-
func NewLRUPolicy(size int) *Policy {
29-
p := Policy{
30-
l: list.New(),
31-
size: size,
32-
ks: make(map[string]*list.Element),
33-
ch: make(chan string, 1),
34-
}
35-
36-
p.ctx, p.cancel = context.WithCancel(context.Background())
37-
p.fn = p.move
38-
39-
return &p
40-
}
41-
42-
func (p *Policy) C() <-chan string {
43-
return p.ch
44-
}
45-
46-
func (p *Policy) Op(k string, op policy.OpType) error {
47-
if p.ctx.Err() != nil {
48-
return policy.ErrClosed
49-
}
50-
51-
p.Lock()
52-
53-
switch op {
54-
case policy.Set:
55-
p.insert(k)
56-
case policy.Get:
57-
p.fn(k)
58-
case policy.Evict:
59-
p.evict(k)
60-
}
61-
62-
p.Unlock()
63-
64-
return nil
65-
}
66-
67-
func (p *Policy) move(k string) {
68-
e, ok := p.ks[k]
69-
70-
if !ok {
71-
return
72-
}
73-
74-
p.l.MoveToBack(e)
75-
}
76-
77-
func (p *Policy) insert(k string) {
78-
if _, ok := p.ks[k]; ok {
79-
return
80-
}
81-
82-
var e *list.Element
83-
84-
if p.l.Len() == 0 {
85-
e = p.l.PushFront(k)
86-
} else {
87-
e = p.l.InsertAfter(k, p.l.Back())
88-
}
89-
90-
p.ks[k] = e
91-
92-
if p.l.Len() > p.size {
93-
v := p.l.Remove(p.l.Front()).(string)
94-
delete(p.ks, v)
95-
96-
select {
97-
case <-p.ctx.Done():
98-
case p.ch <- v:
99-
}
100-
}
101-
}
102-
103-
func (p *Policy) evict(k string) {
104-
e, ok := p.ks[k]
105-
106-
if !ok {
107-
return
108-
}
109-
110-
p.l.Remove(e)
111-
delete(p.ks, k)
112-
}
7+
type Policy = size.Policy
1138

114-
func (p *Policy) Close() error {
115-
p.cancel()
116-
p.closeOnce.Do(func() { close(p.ch) })
117-
return nil
9+
func NewLRUPolicy(sz int) *Policy {
10+
return size.NewLRUPolicy(sz)
11811
}

0 commit comments

Comments
 (0)