package ristretto
import (
"math"
"sync"
"sync/atomic"
"github.com/dgraph-io/ristretto/v2/z"
)
const (
lfuSample = 5
)
func newPolicy[V any ](numCounters , maxCost int64 ) *defaultPolicy [V ] {
return newDefaultPolicy [V ](numCounters , maxCost )
}
type defaultPolicy[V any ] struct {
sync .Mutex
admit *tinyLFU
evict *sampledLFU
itemsCh chan []uint64
stop chan struct {}
done chan struct {}
isClosed bool
metrics *Metrics
}
func newDefaultPolicy[V any ](numCounters , maxCost int64 ) *defaultPolicy [V ] {
p := &defaultPolicy [V ]{
admit : newTinyLFU (numCounters ),
evict : newSampledLFU (maxCost ),
itemsCh : make (chan []uint64 , 3 ),
stop : make (chan struct {}),
done : make (chan struct {}),
}
go p .processItems ()
return p
}
func (p *defaultPolicy [V ]) CollectMetrics (metrics *Metrics ) {
p .metrics = metrics
p .evict .metrics = metrics
}
type policyPair struct {
key uint64
cost int64
}
func (p *defaultPolicy [V ]) processItems () {
for {
select {
case items := <- p .itemsCh :
p .Lock ()
p .admit .Push (items )
p .Unlock ()
case <- p .stop :
p .done <- struct {}{}
return
}
}
}
func (p *defaultPolicy [V ]) Push (keys []uint64 ) bool {
if p .isClosed {
return false
}
if len (keys ) == 0 {
return true
}
select {
case p .itemsCh <- keys :
p .metrics .add (keepGets , keys [0 ], uint64 (len (keys )))
return true
default :
p .metrics .add (dropGets , keys [0 ], uint64 (len (keys )))
return false
}
}
func (p *defaultPolicy [V ]) Add (key uint64 , cost int64 ) ([]*Item [V ], bool ) {
p .Lock ()
defer p .Unlock ()
if cost > p .evict .getMaxCost () {
return nil , false
}
if has := p .evict .updateIfHas (key , cost ); has {
return nil , false
}
room := p .evict .roomLeft (cost )
if room >= 0 {
p .evict .add (key , cost )
p .metrics .add (costAdd , key , uint64 (cost ))
return nil , true
}
incHits := p .admit .Estimate (key )
sample := make ([]*policyPair , 0 , lfuSample )
victims := make ([]*Item [V ], 0 )
for ; room < 0 ; room = p .evict .roomLeft (cost ) {
sample = p .evict .fillSample (sample )
minKey , minHits , minId , minCost := uint64 (0 ), int64 (math .MaxInt64 ), 0 , int64 (0 )
for i , pair := range sample {
if hits := p .admit .Estimate (pair .key ); hits < minHits {
minKey , minHits , minId , minCost = pair .key , hits , i , pair .cost
}
}
if incHits < minHits {
p .metrics .add (rejectSets , key , 1 )
return victims , false
}
p .evict .del (minKey )
sample [minId ] = sample [len (sample )-1 ]
sample = sample [:len (sample )-1 ]
victims = append (victims , &Item [V ]{
Key : minKey ,
Conflict : 0 ,
Cost : minCost ,
})
}
p .evict .add (key , cost )
p .metrics .add (costAdd , key , uint64 (cost ))
return victims , true
}
func (p *defaultPolicy [V ]) Has (key uint64 ) bool {
p .Lock ()
_ , exists := p .evict .keyCosts [key ]
p .Unlock ()
return exists
}
func (p *defaultPolicy [V ]) Del (key uint64 ) {
p .Lock ()
p .evict .del (key )
p .Unlock ()
}
func (p *defaultPolicy [V ]) Cap () int64 {
p .Lock ()
capacity := p .evict .getMaxCost () - p .evict .used
p .Unlock ()
return capacity
}
func (p *defaultPolicy [V ]) Update (key uint64 , cost int64 ) {
p .Lock ()
p .evict .updateIfHas (key , cost )
p .Unlock ()
}
func (p *defaultPolicy [V ]) Cost (key uint64 ) int64 {
p .Lock ()
if cost , found := p .evict .keyCosts [key ]; found {
p .Unlock ()
return cost
}
p .Unlock ()
return -1
}
func (p *defaultPolicy [V ]) Clear () {
p .Lock ()
p .admit .clear ()
p .evict .clear ()
p .Unlock ()
}
func (p *defaultPolicy [V ]) Close () {
if p .isClosed {
return
}
p .stop <- struct {}{}
<-p .done
close (p .stop )
close (p .done )
close (p .itemsCh )
p .isClosed = true
}
func (p *defaultPolicy [V ]) MaxCost () int64 {
if p == nil || p .evict == nil {
return 0
}
return p .evict .getMaxCost ()
}
func (p *defaultPolicy [V ]) UpdateMaxCost (maxCost int64 ) {
if p == nil || p .evict == nil {
return
}
p .evict .updateMaxCost (maxCost )
}
type sampledLFU struct {
maxCost int64
used int64
metrics *Metrics
keyCosts map [uint64 ]int64
}
func newSampledLFU(maxCost int64 ) *sampledLFU {
return &sampledLFU {
keyCosts : make (map [uint64 ]int64 ),
maxCost : maxCost ,
}
}
func (p *sampledLFU ) getMaxCost () int64 {
return atomic .LoadInt64 (&p .maxCost )
}
func (p *sampledLFU ) updateMaxCost (maxCost int64 ) {
atomic .StoreInt64 (&p .maxCost , maxCost )
}
func (p *sampledLFU ) roomLeft (cost int64 ) int64 {
return p .getMaxCost () - (p .used + cost )
}
func (p *sampledLFU ) fillSample (in []*policyPair ) []*policyPair {
if len (in ) >= lfuSample {
return in
}
for key , cost := range p .keyCosts {
in = append (in , &policyPair {key , cost })
if len (in ) >= lfuSample {
return in
}
}
return in
}
func (p *sampledLFU ) del (key uint64 ) {
cost , ok := p .keyCosts [key ]
if !ok {
return
}
p .used -= cost
delete (p .keyCosts , key )
p .metrics .add (costEvict , key , uint64 (cost ))
p .metrics .add (keyEvict , key , 1 )
}
func (p *sampledLFU ) add (key uint64 , cost int64 ) {
p .keyCosts [key ] = cost
p .used += cost
}
func (p *sampledLFU ) updateIfHas (key uint64 , cost int64 ) bool {
if prev , found := p .keyCosts [key ]; found {
p .metrics .add (keyUpdate , key , 1 )
if prev > cost {
diff := prev - cost
p .metrics .add (costAdd , key , ^(uint64 (diff ) - 1 ))
} else if cost > prev {
diff := cost - prev
p .metrics .add (costAdd , key , uint64 (diff ))
}
p .used += cost - prev
p .keyCosts [key ] = cost
return true
}
return false
}
func (p *sampledLFU ) clear () {
p .used = 0
p .keyCosts = make (map [uint64 ]int64 )
}
type tinyLFU struct {
freq *cmSketch
door *z .Bloom
incrs int64
resetAt int64
}
func newTinyLFU(numCounters int64 ) *tinyLFU {
return &tinyLFU {
freq : newCmSketch (numCounters ),
door : z .NewBloomFilter (float64 (numCounters ), 0.01 ),
resetAt : numCounters ,
}
}
func (p *tinyLFU ) Push (keys []uint64 ) {
for _ , key := range keys {
p .Increment (key )
}
}
func (p *tinyLFU ) Estimate (key uint64 ) int64 {
hits := p .freq .Estimate (key )
if p .door .Has (key ) {
hits ++
}
return hits
}
func (p *tinyLFU ) Increment (key uint64 ) {
if added := p .door .AddIfNotHas (key ); !added {
p .freq .Increment (key )
}
p .incrs ++
if p .incrs >= p .resetAt {
p .reset ()
}
}
func (p *tinyLFU ) reset () {
p .incrs = 0
p .door .Clear ()
p .freq .Reset ()
}
func (p *tinyLFU ) clear () {
p .incrs = 0
p .door .Clear ()
p .freq .Clear ()
}
The pages are generated with Golds v0.8.4 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .