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 }