github.com/etecs-ru/ristretto@v0.9.1/stress_test.go (about)

     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/etecs-ru/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  		IgnoreInternalCost: true,
    21  		BufferItems:        64,
    22  		Metrics:            true,
    23  	})
    24  	require.NoError(t, err)
    25  
    26  	for i := 0; i < 100; i++ {
    27  		c.Set(i, i, 1)
    28  	}
    29  	time.Sleep(wait)
    30  	wg := &sync.WaitGroup{}
    31  	for i := 0; i < runtime.GOMAXPROCS(0); i++ {
    32  		wg.Add(1)
    33  		go func() {
    34  			r := rand.New(rand.NewSource(time.Now().UnixNano()))
    35  			for a := 0; a < 1000; a++ {
    36  				k := r.Int() % 10
    37  				if val, ok := c.Get(k); val == nil || !ok {
    38  					err = fmt.Errorf("expected %d but got nil", k)
    39  					break
    40  				} else if val != nil && val.(int) != k {
    41  					err = fmt.Errorf("expected %d but got %d", k, val.(int))
    42  					break
    43  				}
    44  			}
    45  			wg.Done()
    46  		}()
    47  	}
    48  	wg.Wait()
    49  	require.NoError(t, err)
    50  	require.Equal(t, 1.0, c.Metrics.Ratio())
    51  }
    52  
    53  func TestStressHitRatio(t *testing.T) {
    54  	key := sim.NewZipfian(1.0001, 1, 1000)
    55  	c, err := NewCache(&Config{
    56  		NumCounters: 1000,
    57  		MaxCost:     100,
    58  		BufferItems: 64,
    59  		Metrics:     true,
    60  	})
    61  	require.NoError(t, err)
    62  
    63  	o := NewClairvoyant(100)
    64  	for i := 0; i < 10000; i++ {
    65  		k, err := key()
    66  		require.NoError(t, err)
    67  
    68  		if _, ok := o.Get(k); !ok {
    69  			o.Set(k, k, 1)
    70  		}
    71  		if _, ok := c.Get(k); !ok {
    72  			c.Set(k, k, 1)
    73  		}
    74  	}
    75  	t.Logf("actual: %.2f, optimal: %.2f", c.Metrics.Ratio(), o.Metrics().Ratio())
    76  }
    77  
    78  // Clairvoyant is a mock cache providing us with optimal hit ratios to compare
    79  // with Ristretto's. It looks ahead and evicts the absolute least valuable item,
    80  // which we try to approximate in a real cache.
    81  type Clairvoyant struct {
    82  	hits     map[uint64]uint64
    83  	access   []uint64
    84  	capacity uint64
    85  }
    86  
    87  func NewClairvoyant(capacity uint64) *Clairvoyant {
    88  	return &Clairvoyant{
    89  		capacity: capacity,
    90  		hits:     make(map[uint64]uint64),
    91  		access:   make([]uint64, 0),
    92  	}
    93  }
    94  
    95  // Get just records the cache access so that we can later take this event into
    96  // consideration when calculating the absolute least valuable item to evict.
    97  func (c *Clairvoyant) Get(key interface{}) (interface{}, bool) {
    98  	c.hits[key.(uint64)]++
    99  	c.access = append(c.access, key.(uint64))
   100  	return nil, false
   101  }
   102  
   103  // Set isn't important because it is only called after a Get (in the case of our
   104  // hit ratio benchmarks, at least).
   105  func (c *Clairvoyant) Set(key, value interface{}, cost int64) bool {
   106  	return false
   107  }
   108  
   109  func (c *Clairvoyant) Metrics() *Metrics {
   110  	stat := newMetrics()
   111  	look := make(map[uint64]struct{}, c.capacity)
   112  	data := &clairvoyantHeap{}
   113  	heap.Init(data)
   114  	for _, key := range c.access {
   115  		if _, has := look[key]; has {
   116  			stat.add(hit, 0, 1)
   117  			continue
   118  		}
   119  		if uint64(data.Len()) >= c.capacity {
   120  			victim := heap.Pop(data)
   121  			delete(look, victim.(*clairvoyantItem).key)
   122  		}
   123  		stat.add(miss, 0, 1)
   124  		look[key] = struct{}{}
   125  		heap.Push(data, &clairvoyantItem{key, c.hits[key]})
   126  	}
   127  	return stat
   128  }
   129  
   130  type clairvoyantItem struct {
   131  	key  uint64
   132  	hits uint64
   133  }
   134  
   135  type clairvoyantHeap []*clairvoyantItem
   136  
   137  func (h clairvoyantHeap) Len() int           { return len(h) }
   138  func (h clairvoyantHeap) Less(i, j int) bool { return h[i].hits < h[j].hits }
   139  func (h clairvoyantHeap) Swap(i, j int)      { h[i], h[j] = h[j], h[i] }
   140  
   141  func (h *clairvoyantHeap) Push(x interface{}) {
   142  	*h = append(*h, x.(*clairvoyantItem))
   143  }
   144  
   145  func (h *clairvoyantHeap) Pop() interface{} {
   146  	old := *h
   147  	n := len(old)
   148  	x := old[n-1]
   149  	*h = old[0 : n-1]
   150  	return x
   151  }