github.com/pingcap/badger@v1.5.1-0.20230103063557-828f39b09b6d/cache/stress_test.go (about) 1 package cache 2 3 import ( 4 "container/heap" 5 "fmt" 6 "math/rand" 7 "runtime" 8 "sync" 9 "testing" 10 "time" 11 12 "github.com/pingcap/badger/cache/sim" 13 ) 14 15 func TestStressSetGet(t *testing.T) { 16 c, err := NewCache(&Config{ 17 NumCounters: 1000, 18 MaxCost: 100, 19 BufferItems: 64, 20 Metrics: true, 21 }) 22 if err != nil { 23 panic(err) 24 } 25 for i := uint64(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.Uint64() % 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.(uint64) != 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 if err != nil { 49 t.Fatal(err) 50 } 51 if r := c.Metrics.Ratio(); r != 1.0 { 52 t.Fatalf("hit ratio should be 1.0 but got %.2f\n", r) 53 } 54 } 55 56 func TestStressHitRatio(t *testing.T) { 57 key := sim.NewZipfian(1.0001, 1, 1000) 58 c, err := NewCache(&Config{ 59 NumCounters: 1000, 60 MaxCost: 100, 61 BufferItems: 64, 62 Metrics: true, 63 }) 64 if err != nil { 65 panic(err) 66 } 67 o := NewClairvoyant(100) 68 for i := 0; i < 10000; i++ { 69 k, err := key() 70 if err != nil { 71 panic(err) 72 } 73 if _, ok := o.Get(k); !ok { 74 o.Set(k, k, 1) 75 } 76 if _, ok := c.Get(k); !ok { 77 c.Set(k, k, 1) 78 } 79 } 80 t.Logf("actual: %.2f, optimal: %.2f", c.Metrics.Ratio(), o.Metrics().Ratio()) 81 } 82 83 // Clairvoyant is a mock cache providing us with optimal hit ratios to compare 84 // with Ristretto's. It looks ahead and evicts the absolute least valuable item, 85 // which we try to approximate in a real cache. 86 type Clairvoyant struct { 87 capacity uint64 88 hits map[uint64]uint64 89 access []uint64 90 } 91 92 func NewClairvoyant(capacity uint64) *Clairvoyant { 93 return &Clairvoyant{ 94 capacity: capacity, 95 hits: make(map[uint64]uint64), 96 access: make([]uint64, 0), 97 } 98 } 99 100 // Get just records the cache access so that we can later take this event into 101 // consideration when calculating the absolute least valuable item to evict. 102 func (c *Clairvoyant) Get(key interface{}) (interface{}, bool) { 103 c.hits[key.(uint64)]++ 104 c.access = append(c.access, key.(uint64)) 105 return nil, false 106 } 107 108 // Set isn't important because it is only called after a Get (in the case of our 109 // hit ratio benchmarks, at least). 110 func (c *Clairvoyant) Set(key, value interface{}, cost int64) bool { 111 return false 112 } 113 114 func (c *Clairvoyant) Metrics() *Metrics { 115 stat := newMetrics() 116 look := make(map[uint64]struct{}, c.capacity) 117 data := &clairvoyantHeap{} 118 heap.Init(data) 119 for _, key := range c.access { 120 if _, has := look[key]; has { 121 stat.add(hit, 0, 1) 122 continue 123 } 124 if uint64(data.Len()) >= c.capacity { 125 victim := heap.Pop(data) 126 delete(look, victim.(*clairvoyantItem).key) 127 } 128 stat.add(miss, 0, 1) 129 look[key] = struct{}{} 130 heap.Push(data, &clairvoyantItem{key, c.hits[key]}) 131 } 132 return stat 133 } 134 135 type clairvoyantItem struct { 136 key uint64 137 hits uint64 138 } 139 140 type clairvoyantHeap []*clairvoyantItem 141 142 func (h clairvoyantHeap) Len() int { return len(h) } 143 func (h clairvoyantHeap) Less(i, j int) bool { return h[i].hits < h[j].hits } 144 func (h clairvoyantHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] } 145 146 func (h *clairvoyantHeap) Push(x interface{}) { 147 *h = append(*h, x.(*clairvoyantItem)) 148 } 149 150 func (h *clairvoyantHeap) Pop() interface{} { 151 old := *h 152 n := len(old) 153 x := old[n-1] 154 *h = old[0 : n-1] 155 return x 156 }