package clock import ( "hash/maphash" "runtime" "lindenii.org/go/lgo/intconv" ) // maxShards bounds the shard count. // // Keep it relatively modest // so the per-shard budget // stays large enough to admit sizable values. const maxShards = 16 // WeightFunc reports one entry's weight, used for eviction budgeting. type WeightFunc[K comparable, V any] func(key K, value V) uint64 // Cache is a concurrent, weight-bounded cache // with CLOCK eviction. // // Reads are lock-free; // writes lock only the shard that owns the key. // // Labels: MT-Safe. type Cache[K comparable, V any] struct { shards []*shard[K, V] seed maphash.Seed mask uint64 weightFn WeightFunc[K, V] } // New returns a cache bounded to maxWeight total weight, // weighing entries with weightFn. // // New panics if weightFn is nil. func New[K comparable, V any](maxWeight uint64, weightFn WeightFunc[K, V]) *Cache[K, V] { if weightFn == nil { panic("internal/clock: nil weight function") } count, mask := shardLayout(maxWeight) perShard := maxWeight / (mask + 1) shards := make([]*shard[K, V], count) for i := range shards { shards[i] = newShard[K, V](perShard) } return &Cache[K, V]{ shards: shards, seed: maphash.MakeSeed(), mask: mask, weightFn: weightFn, } } // shardLayout picks a power-of-two shard count and its address mask. // // Tracks GOMAXPROCS, capped at maxShards, // and is shrunk so the per-shard budget // stays at least one while maxWeight is nonzero. func shardLayout(maxWeight uint64) (int, uint64) { count := 1 for count < runtime.GOMAXPROCS(0) && count < maxShards { count *= 2 } countU, err := intconv.IntToUint64(count) if err != nil { return 1, 0 } for countU > maxWeight && countU > 1 { count /= 2 countU /= 2 } return count, countU - 1 } // shardFor returns the shard that owns key. func (cache *Cache[K, V]) shardFor(key K) *shard[K, V] { return cache.shards[maphash.Comparable(cache.seed, key)&cache.mask] }