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  }