github.com/angenalZZZ/gofunc@v0.0.0-20210507121333-48ff1be3917b/data/cache/fastcache/fastcache.go (about)

     1  // The package has been extracted from https://victoriametrics.com/
     2  package fastcache
     3  
     4  import (
     5  	"fmt"
     6  	"sync"
     7  	"sync/atomic"
     8  
     9  	"github.com/cespare/xxhash/v2"
    10  )
    11  
    12  const bucketsCount = 512
    13  
    14  const chunkSize = 64 * 1024
    15  
    16  const bucketSizeBits = 40
    17  
    18  const genSizeBits = 64 - bucketSizeBits
    19  
    20  const maxGen = 1<<genSizeBits - 1
    21  
    22  const maxBucketSize uint64 = 1 << bucketSizeBits
    23  
    24  // Stats represents cache stats.
    25  //
    26  // Use Cache.UpdateStats for obtaining fresh stats from the cache.
    27  type Stats struct {
    28  	// GetCalls is the number of Get calls.
    29  	GetCalls uint64
    30  
    31  	// SetCalls is the number of Set calls.
    32  	SetCalls uint64
    33  
    34  	// Misses is the number of cache misses.
    35  	Misses uint64
    36  
    37  	// Collisions is the number of cache collisions.
    38  	//
    39  	// Usually the number of collisions must be close to zero.
    40  	// High number of collisions suggest something wrong with cache.
    41  	Collisions uint64
    42  
    43  	// Corruptions is the number of detected corruptions of the cache.
    44  	//
    45  	// Corruptions may occur when corrupted cache is loaded from file.
    46  	Corruptions uint64
    47  
    48  	// EntriesCount is the current number of entries in the cache.
    49  	EntriesCount uint64
    50  
    51  	// BytesSize is the current size of the cache in bytes.
    52  	BytesSize uint64
    53  
    54  	// BigStats contains stats for GetBig/SetBig methods.
    55  	BigStats
    56  }
    57  
    58  // Reset resets s, so it may be re-used again in Cache.UpdateStats.
    59  func (s *Stats) Reset() {
    60  	*s = Stats{}
    61  }
    62  
    63  // BigStats contains stats for GetBig/SetBig methods.
    64  type BigStats struct {
    65  	// GetBigCalls is the number of GetBig calls.
    66  	GetBigCalls uint64
    67  
    68  	// SetBigCalls is the number of SetBig calls.
    69  	SetBigCalls uint64
    70  
    71  	// TooBigKeyErrors is the number of calls to SetBig with too big key.
    72  	TooBigKeyErrors uint64
    73  
    74  	// InvalidMetavalueErrors is the number of calls to GetBig resulting
    75  	// to invalid metavalue.
    76  	InvalidMetavalueErrors uint64
    77  
    78  	// InvalidValueLenErrors is the number of calls to GetBig resulting
    79  	// to a chunk with invalid length.
    80  	InvalidValueLenErrors uint64
    81  
    82  	// InvalidValueHashErrors is the number of calls to GetBig resulting
    83  	// to a chunk with invalid hash value.
    84  	InvalidValueHashErrors uint64
    85  }
    86  
    87  func (bs *BigStats) reset() {
    88  	atomic.StoreUint64(&bs.GetBigCalls, 0)
    89  	atomic.StoreUint64(&bs.SetBigCalls, 0)
    90  	atomic.StoreUint64(&bs.TooBigKeyErrors, 0)
    91  	atomic.StoreUint64(&bs.InvalidMetavalueErrors, 0)
    92  	atomic.StoreUint64(&bs.InvalidValueLenErrors, 0)
    93  	atomic.StoreUint64(&bs.InvalidValueHashErrors, 0)
    94  }
    95  
    96  // Cache is a fast thread-safe inmemory cache optimized for big number
    97  // of entries.
    98  //
    99  // It has much lower impact on GC comparing to a simple `map[string][]byte`.
   100  //
   101  // Use New or LoadFromFile* for creating new cache instance.
   102  // Concurrent goroutines may call any Cache methods on the same cache instance.
   103  //
   104  // Call Reset when the cache is no longer needed. This reclaims the allocated
   105  // memory.
   106  type Cache struct {
   107  	buckets [bucketsCount]bucket
   108  
   109  	bigStats BigStats
   110  }
   111  
   112  // New returns new cache with the given maxBytes capacity in bytes.
   113  //
   114  // maxBytes must be smaller than the available RAM size for the app,
   115  // since the cache holds data in memory.
   116  //
   117  // If maxBytes is less than 32MB, then the minimum cache capacity is 32MB.
   118  func New(maxBytes int) *Cache {
   119  	if maxBytes <= 0 {
   120  		panic(fmt.Errorf("maxBytes must be greater than 0; got %d", maxBytes))
   121  	}
   122  	var c Cache
   123  	maxBucketBytes := uint64((maxBytes + bucketsCount - 1) / bucketsCount)
   124  	for i := range c.buckets[:] {
   125  		c.buckets[i].Init(maxBucketBytes)
   126  	}
   127  	return &c
   128  }
   129  
   130  // Set stores (k, v) in the cache.
   131  //
   132  // Get must be used for reading the stored entry.
   133  //
   134  // The stored entry may be evicted at any time either due to cache
   135  // overflow or due to unlikely hash collision.
   136  // Pass higher maxBytes value to New if the added items disappear
   137  // frequently.
   138  //
   139  // (k, v) entries with summary size exceeding 64KB aren't stored in the cache.
   140  // SetBig can be used for storing entries exceeding 64KB.
   141  //
   142  // k and v contents may be modified after returning from Set.
   143  func (c *Cache) Set(k, v []byte) {
   144  	h := xxhash.Sum64(k)
   145  	idx := h % bucketsCount
   146  	c.buckets[idx].Set(k, v, h)
   147  }
   148  
   149  // Get appends value by the key k to dst and returns the result.
   150  //
   151  // Get allocates new byte slice for the returned value if dst is nil.
   152  //
   153  // Get returns only values stored in c via Set.
   154  //
   155  // k contents may be modified after returning from Get.
   156  func (c *Cache) Get(dst, k []byte) []byte {
   157  	h := xxhash.Sum64(k)
   158  	idx := h % bucketsCount
   159  	dst, _ = c.buckets[idx].Get(dst, k, h, true)
   160  	return dst
   161  }
   162  
   163  // HasGet works identically to Get, but also returns whether the given key
   164  // exists in the cache. This method makes it possible to differentiate between a
   165  // stored nil/empty value versus and non-existing value.
   166  func (c *Cache) HasGet(dst, k []byte) ([]byte, bool) {
   167  	h := xxhash.Sum64(k)
   168  	idx := h % bucketsCount
   169  	return c.buckets[idx].Get(dst, k, h, true)
   170  }
   171  
   172  // Has returns true if entry for the given key k exists in the cache.
   173  func (c *Cache) Has(k []byte) bool {
   174  	h := xxhash.Sum64(k)
   175  	idx := h % bucketsCount
   176  	_, ok := c.buckets[idx].Get(nil, k, h, false)
   177  	return ok
   178  }
   179  
   180  // Del deletes value for the given k from the cache.
   181  //
   182  // k contents may be modified after returning from Del.
   183  func (c *Cache) Del(k []byte) {
   184  	h := xxhash.Sum64(k)
   185  	idx := h % bucketsCount
   186  	c.buckets[idx].Del(h)
   187  }
   188  
   189  // Reset removes all the items from the cache.
   190  func (c *Cache) Reset() {
   191  	for i := range c.buckets[:] {
   192  		c.buckets[i].Reset()
   193  	}
   194  	c.bigStats.reset()
   195  }
   196  
   197  // UpdateStats adds cache stats to s.
   198  //
   199  // Call s.Reset before calling UpdateStats if s is re-used.
   200  func (c *Cache) UpdateStats(s *Stats) {
   201  	for i := range c.buckets[:] {
   202  		c.buckets[i].UpdateStats(s)
   203  	}
   204  	s.GetBigCalls += atomic.LoadUint64(&c.bigStats.GetBigCalls)
   205  	s.SetBigCalls += atomic.LoadUint64(&c.bigStats.SetBigCalls)
   206  	s.TooBigKeyErrors += atomic.LoadUint64(&c.bigStats.TooBigKeyErrors)
   207  	s.InvalidMetavalueErrors += atomic.LoadUint64(&c.bigStats.InvalidMetavalueErrors)
   208  	s.InvalidValueLenErrors += atomic.LoadUint64(&c.bigStats.InvalidValueLenErrors)
   209  	s.InvalidValueHashErrors += atomic.LoadUint64(&c.bigStats.InvalidValueHashErrors)
   210  }
   211  
   212  type bucket struct {
   213  	mu sync.RWMutex
   214  
   215  	// chunks is a ring buffer with encoded (k, v) pairs.
   216  	// It consists of 64KB chunks.
   217  	chunks [][]byte
   218  
   219  	// m maps hash(k) to idx of (k, v) pair in chunks.
   220  	m map[uint64]uint64
   221  
   222  	// idx points to chunks for writing the next (k, v) pair.
   223  	idx uint64
   224  
   225  	// gen is the generation of chunks.
   226  	gen uint64
   227  
   228  	getCalls    uint64
   229  	setCalls    uint64
   230  	misses      uint64
   231  	collisions  uint64
   232  	corruptions uint64
   233  }
   234  
   235  func (b *bucket) Init(maxBytes uint64) {
   236  	if maxBytes == 0 {
   237  		panic(fmt.Errorf("maxBytes cannot be zero"))
   238  	}
   239  	if maxBytes >= maxBucketSize {
   240  		panic(fmt.Errorf("too big maxBytes=%d; should be smaller than %d", maxBytes, maxBucketSize))
   241  	}
   242  	maxChunks := (maxBytes + chunkSize - 1) / chunkSize
   243  	b.chunks = make([][]byte, maxChunks)
   244  	b.m = make(map[uint64]uint64)
   245  	b.Reset()
   246  }
   247  
   248  func (b *bucket) Reset() {
   249  	b.mu.Lock()
   250  	chunks := b.chunks
   251  	for i := range chunks {
   252  		//putChunk(chunks[i])
   253  		chunks[i] = nil
   254  	}
   255  	bm := b.m
   256  	for k := range bm {
   257  		delete(bm, k)
   258  	}
   259  	b.idx = 0
   260  	b.gen = 1
   261  	atomic.StoreUint64(&b.getCalls, 0)
   262  	atomic.StoreUint64(&b.setCalls, 0)
   263  	atomic.StoreUint64(&b.misses, 0)
   264  	atomic.StoreUint64(&b.collisions, 0)
   265  	atomic.StoreUint64(&b.corruptions, 0)
   266  	b.mu.Unlock()
   267  }
   268  
   269  func (b *bucket) Clean() {
   270  	b.mu.Lock()
   271  	bGen := b.gen & ((1 << genSizeBits) - 1)
   272  	bIdx := b.idx
   273  	bm := b.m
   274  	for k, v := range bm {
   275  		gen := v >> bucketSizeBits
   276  		idx := v & ((1 << bucketSizeBits) - 1)
   277  		if gen == bGen && idx < bIdx || gen+1 == bGen && idx >= bIdx || gen == maxGen && bGen == 1 && idx >= bIdx {
   278  			continue
   279  		}
   280  		delete(bm, k)
   281  	}
   282  	b.mu.Unlock()
   283  }
   284  
   285  func (b *bucket) UpdateStats(s *Stats) {
   286  	s.GetCalls += atomic.LoadUint64(&b.getCalls)
   287  	s.SetCalls += atomic.LoadUint64(&b.setCalls)
   288  	s.Misses += atomic.LoadUint64(&b.misses)
   289  	s.Collisions += atomic.LoadUint64(&b.collisions)
   290  	s.Corruptions += atomic.LoadUint64(&b.corruptions)
   291  
   292  	b.mu.RLock()
   293  	s.EntriesCount += uint64(len(b.m))
   294  	for _, chunk := range b.chunks {
   295  		s.BytesSize += uint64(cap(chunk))
   296  	}
   297  	b.mu.RUnlock()
   298  }
   299  
   300  func (b *bucket) Set(k, v []byte, h uint64) {
   301  	setCalls := atomic.AddUint64(&b.setCalls, 1)
   302  	if setCalls%16384 == 0 {
   303  		//if setCalls%(1<<14) == 0 {
   304  		b.Clean()
   305  	}
   306  	// key string is changed, larger than 256 bits are not accepted.
   307  	if len(k) >= 256 || len(v) >= 65536 {
   308  		//if len(k) >= (1<<16) || len(v) >= (1<<16) {
   309  		// Too big key or value - its length cannot be encoded
   310  		// with 2 bytes (see below). Skip the entry.
   311  		return
   312  	}
   313  	var kvLenBuf [4]byte
   314  	kvLenBuf[0] = byte(uint16(len(k)) >> 8)
   315  	kvLenBuf[1] = byte(len(k))
   316  	kvLenBuf[2] = byte(uint16(len(v)) >> 8)
   317  	kvLenBuf[3] = byte(len(v))
   318  	kvLen := uint64(len(kvLenBuf) + len(k) + len(v))
   319  	if kvLen >= chunkSize {
   320  		// Do not store too big keys and values, since they do not
   321  		// fit a chunk.
   322  		return
   323  	}
   324  
   325  	b.mu.Lock()
   326  	idx := b.idx
   327  	idxNew := idx + kvLen
   328  	chunkIdx := idx / chunkSize
   329  	chunkIdxNew := idxNew / chunkSize
   330  	if chunkIdxNew > chunkIdx {
   331  		if chunkIdxNew >= uint64(len(b.chunks)) {
   332  			idx = 0
   333  			idxNew = kvLen
   334  			chunkIdx = 0
   335  			b.gen++
   336  			if b.gen&((1<<genSizeBits)-1) == 0 {
   337  				b.gen++
   338  			}
   339  		} else {
   340  			idx = chunkIdxNew * chunkSize
   341  			idxNew = idx + kvLen
   342  			chunkIdx = chunkIdxNew
   343  		}
   344  		b.chunks[chunkIdx] = b.chunks[chunkIdx][:0]
   345  	}
   346  	chunk := b.chunks[chunkIdx]
   347  	if chunk == nil {
   348  		chunk = make([]byte, chunkSize)
   349  		//chunk = getChunk()
   350  		chunk = chunk[:0]
   351  	}
   352  	chunk = append(chunk, kvLenBuf[:]...)
   353  	chunk = append(chunk, k...)
   354  	chunk = append(chunk, v...)
   355  	b.chunks[chunkIdx] = chunk
   356  	b.m[h] = idx | (b.gen << bucketSizeBits)
   357  	b.idx = idxNew
   358  	b.mu.Unlock()
   359  }
   360  
   361  func (b *bucket) Get(dst, k []byte, h uint64, returnDst bool) ([]byte, bool) {
   362  	atomic.AddUint64(&b.getCalls, 1)
   363  	found := false
   364  	b.mu.RLock()
   365  	v := b.m[h]
   366  	bGen := b.gen & ((1 << genSizeBits) - 1)
   367  	if v > 0 {
   368  		gen := v >> bucketSizeBits
   369  		idx := v & ((1 << bucketSizeBits) - 1)
   370  		if gen == bGen && idx < b.idx || gen+1 == bGen && idx >= b.idx || gen == maxGen && bGen == 1 && idx >= b.idx {
   371  			chunkIdx := idx / chunkSize
   372  			if chunkIdx >= uint64(len(b.chunks)) {
   373  				// Corrupted data during the load from file. Just skip it.
   374  				atomic.AddUint64(&b.corruptions, 1)
   375  				goto end
   376  			}
   377  			chunk := b.chunks[chunkIdx]
   378  			idx %= chunkSize
   379  			if idx+4 >= chunkSize {
   380  				// Corrupted data during the load from file. Just skip it.
   381  				atomic.AddUint64(&b.corruptions, 1)
   382  				goto end
   383  			}
   384  			kvLenBuf := chunk[idx : idx+4]
   385  			keyLen := (uint64(kvLenBuf[0]) << 8) | uint64(kvLenBuf[1])
   386  			valLen := (uint64(kvLenBuf[2]) << 8) | uint64(kvLenBuf[3])
   387  			idx += 4
   388  			if idx+keyLen+valLen >= chunkSize {
   389  				// Corrupted data during the load from file. Just skip it.
   390  				atomic.AddUint64(&b.corruptions, 1)
   391  				goto end
   392  			}
   393  			if string(k) == string(chunk[idx:idx+keyLen]) {
   394  				idx += keyLen
   395  				if returnDst {
   396  					dst = append(dst, chunk[idx:idx+valLen]...)
   397  				}
   398  				found = true
   399  			} else {
   400  				atomic.AddUint64(&b.collisions, 1)
   401  			}
   402  		}
   403  	}
   404  end:
   405  	b.mu.RUnlock()
   406  	if !found {
   407  		atomic.AddUint64(&b.misses, 1)
   408  	}
   409  	return dst, found
   410  }
   411  
   412  func (b *bucket) Del(h uint64) {
   413  	b.mu.Lock()
   414  	delete(b.m, h)
   415  	b.mu.Unlock()
   416  }