1 package ristretto
2
3 import (
4 "container/heap"
5 "fmt"
6 "math/rand"
7 "runtime"
8 "sync"
9 "testing"
10 "time"
11
12 "github.com/dgraph-io/ristretto/sim"
13 "github.com/stretchr/testify/require"
14 )
15
16 func TestStressSetGet(t *testing.T) {
17 c, err := NewCache(&Config{
18 NumCounters: 1000,
19 MaxCost: 100,
20 BufferItems: 64,
21 Metrics: true,
22 })
23 require.NoError(t, err)
24
25 for i := 0; i < 100; i++ {
26 c.Set(i, i, 1)
27 }
28 time.Sleep(wait)
29 wg := &sync.WaitGroup{}
30 for i := 0; i < runtime.GOMAXPROCS(0); i++ {
31 wg.Add(1)
32 go func() {
33 r := rand.New(rand.NewSource(time.Now().UnixNano()))
34 for a := 0; a < 1000; a++ {
35 k := r.Int() % 10
36 if val, ok := c.Get(k); val == nil || !ok {
37 err = fmt.Errorf("expected %d but got nil", k)
38 break
39 } else if val != nil && val.(int) != k {
40 err = fmt.Errorf("expected %d but got %d", k, val.(int))
41 break
42 }
43 }
44 wg.Done()
45 }()
46 }
47 wg.Wait()
48 require.NoError(t, err)
49 require.Equal(t, 1.0, c.Metrics.Ratio())
50 }
51
52 func TestStressHitRatio(t *testing.T) {
53 key := sim.NewZipfian(1.0001, 1, 1000)
54 c, err := NewCache(&Config{
55 NumCounters: 1000,
56 MaxCost: 100,
57 BufferItems: 64,
58 Metrics: true,
59 })
60 require.NoError(t, err)
61
62 o := NewClairvoyant(100)
63 for i := 0; i < 10000; i++ {
64 k, err := key()
65 require.NoError(t, err)
66
67 if _, ok := o.Get(k); !ok {
68 o.Set(k, k, 1)
69 }
70 if _, ok := c.Get(k); !ok {
71 c.Set(k, k, 1)
72 }
73 }
74 t.Logf("actual: %.2f, optimal: %.2f", c.Metrics.Ratio(), o.Metrics().Ratio())
75 }
76
77
78
79
80 type Clairvoyant struct {
81 capacity uint64
82 hits map[uint64]uint64
83 access []uint64
84 }
85
86 func NewClairvoyant(capacity uint64) *Clairvoyant {
87 return &Clairvoyant{
88 capacity: capacity,
89 hits: make(map[uint64]uint64),
90 access: make([]uint64, 0),
91 }
92 }
93
94
95
96 func (c *Clairvoyant) Get(key interface{}) (interface{}, bool) {
97 c.hits[key.(uint64)]++
98 c.access = append(c.access, key.(uint64))
99 return nil, false
100 }
101
102
103
104 func (c *Clairvoyant) Set(key, value interface{}, cost int64) bool {
105 return false
106 }
107
108 func (c *Clairvoyant) Metrics() *Metrics {
109 stat := newMetrics()
110 look := make(map[uint64]struct{}, c.capacity)
111 data := &clairvoyantHeap{}
112 heap.Init(data)
113 for _, key := range c.access {
114 if _, has := look[key]; has {
115 stat.add(hit, 0, 1)
116 continue
117 }
118 if uint64(data.Len()) >= c.capacity {
119 victim := heap.Pop(data)
120 delete(look, victim.(*clairvoyantItem).key)
121 }
122 stat.add(miss, 0, 1)
123 look[key] = struct{}{}
124 heap.Push(data, &clairvoyantItem{key, c.hits[key]})
125 }
126 return stat
127 }
128
129 type clairvoyantItem struct {
130 key uint64
131 hits uint64
132 }
133
134 type clairvoyantHeap []*clairvoyantItem
135
136 func (h clairvoyantHeap) Len() int { return len(h) }
137 func (h clairvoyantHeap) Less(i, j int) bool { return h[i].hits < h[j].hits }
138 func (h clairvoyantHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
139
140 func (h *clairvoyantHeap) Push(x interface{}) {
141 *h = append(*h, x.(*clairvoyantItem))
142 }
143
144 func (h *clairvoyantHeap) Pop() interface{} {
145 old := *h
146 n := len(old)
147 x := old[n-1]
148 *h = old[0 : n-1]
149 return x
150 }
151
View as plain text