github.com/matrixorigin/matrixone@v1.2.0/pkg/fileservice/memorycache/lrucache/lru.go (about)

     1  // Copyright 2022 Matrix Origin
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package lrucache
    16  
    17  import (
    18  	"context"
    19  	"runtime"
    20  	"sync/atomic"
    21  
    22  	"github.com/dolthub/maphash"
    23  	"github.com/matrixorigin/matrixone/pkg/fileservice/memorycache/lrucache/internal/hashmap"
    24  )
    25  
    26  func New[K comparable, V BytesLike](
    27  	capacity int64,
    28  	postSet func(keySet K, valSet V),
    29  	postGet func(key K, value V),
    30  	postEvict func(keyEvicted K, valEvicted V),
    31  ) *LRU[K, V] {
    32  	var l LRU[K, V]
    33  
    34  	// How many cache shards should we create?
    35  	//
    36  	// Note that the probability two processors will try to access the same
    37  	// shard at the same time increases superlinearly with the number of
    38  	// processors (Eg, consider the brithday problem where each CPU is a person,
    39  	// and each shard is a possible birthday).
    40  	//
    41  	// We could consider growing the number of shards superlinearly, but
    42  	// increasing the shard count may reduce the effectiveness of the caching
    43  	// algorithm if frequently-accessed blocks are insufficiently distributed
    44  	// across shards. If a shard's size is smaller than a single frequently
    45  	// scanned sstable, then the shard will be unable to hold the entire
    46  	// frequently-scanned table in memory despite other shards still holding
    47  	// infrequently accessed blocks.
    48  	//
    49  	// Experimentally, we've observed contention contributing to tail latencies
    50  	// at 2 shards per processor. For now we use 4 shards per processor,
    51  	// recognizing this may not be final word.
    52  	m := 4 * runtime.GOMAXPROCS(0)
    53  	pool := newPool[K, V](func() *lruItem[K, V] {
    54  		return &lruItem[K, V]{}
    55  	})
    56  	l.capacity = capacity
    57  	l.hasher = maphash.NewHasher[K]()
    58  	l.shards = make([]shard[K, V], m)
    59  	for i := range l.shards {
    60  		l.shards[i].totalSize = &l.size
    61  		l.shards[i].pool = pool
    62  		l.shards[i].postSet = postSet
    63  		l.shards[i].postGet = postGet
    64  		l.shards[i].postEvict = postEvict
    65  		l.shards[i].evicts = newList[K, V]()
    66  		l.shards[i].capacity = capacity / int64(len(l.shards))
    67  		if l.shards[i].capacity < 1 {
    68  			l.shards[i].capacity = 1
    69  		}
    70  		l.shards[i].kv = hashmap.New[K, lruItem[K, V]](0)
    71  	}
    72  	return &l
    73  }
    74  
    75  func (l *LRU[K, V]) Set(ctx context.Context, key K, value V) {
    76  	var s *shard[K, V]
    77  
    78  	h := l.hash(key)
    79  	s = &l.shards[h%uint64(len(l.shards))]
    80  	s.Set(ctx, h, key, value)
    81  }
    82  
    83  func (l *LRU[K, V]) Get(ctx context.Context, key K) (value V, ok bool) {
    84  	var s *shard[K, V]
    85  
    86  	h := l.hash(key)
    87  	s = &l.shards[h%uint64(len(l.shards))]
    88  	return s.Get(ctx, h, key)
    89  }
    90  
    91  func (l *LRU[K, V]) Flush() {
    92  	for i := range l.shards {
    93  		l.shards[i].Flush()
    94  	}
    95  }
    96  
    97  func (l *LRU[K, V]) DeletePaths(ctx context.Context, paths []string) {
    98  	//TODO
    99  }
   100  
   101  func (l *LRU[K, V]) Capacity() int64 {
   102  	return l.capacity
   103  }
   104  
   105  func (l *LRU[K, V]) Used() int64 {
   106  	return atomic.LoadInt64(&l.size)
   107  }
   108  
   109  func (l *LRU[K, V]) Available() int64 {
   110  	return l.capacity - atomic.LoadInt64(&l.size)
   111  }
   112  
   113  func (l *LRU[K, V]) hash(k K) uint64 {
   114  	return l.hasher.Hash(k)
   115  }