package lru
import (
"errors"
"sync"
"github.com/hashicorp/golang-lru/v2/simplelru"
)
const (
Default2QRecentRatio = 0.25
Default2QGhostEntries = 0.50
)
type TwoQueueCache [K comparable , V any ] struct {
size int
recentSize int
recentRatio float64
ghostRatio float64
recent simplelru .LRUCache [K , V ]
frequent simplelru .LRUCache [K , V ]
recentEvict simplelru .LRUCache [K , struct {}]
lock sync .RWMutex
}
func New2Q [K comparable , V any ](size int ) (*TwoQueueCache [K , V ], error ) {
return New2QParams [K , V ](size , Default2QRecentRatio , Default2QGhostEntries )
}
func New2QParams [K comparable , V any ](size int , recentRatio , ghostRatio float64 ) (*TwoQueueCache [K , V ], error ) {
if size <= 0 {
return nil , errors .New ("invalid size" )
}
if recentRatio < 0.0 || recentRatio > 1.0 {
return nil , errors .New ("invalid recent ratio" )
}
if ghostRatio < 0.0 || ghostRatio > 1.0 {
return nil , errors .New ("invalid ghost ratio" )
}
recentSize := int (float64 (size ) * recentRatio )
evictSize := int (float64 (size ) * ghostRatio )
recent , err := simplelru .NewLRU [K , V ](size , nil )
if err != nil {
return nil , err
}
frequent , err := simplelru .NewLRU [K , V ](size , nil )
if err != nil {
return nil , err
}
recentEvict , err := simplelru .NewLRU [K , struct {}](evictSize , nil )
if err != nil {
return nil , err
}
c := &TwoQueueCache [K , V ]{
size : size ,
recentSize : recentSize ,
recentRatio : recentRatio ,
ghostRatio : ghostRatio ,
recent : recent ,
frequent : frequent ,
recentEvict : recentEvict ,
}
return c , nil
}
func (c *TwoQueueCache [K , V ]) Get (key K ) (value V , ok bool ) {
c .lock .Lock ()
defer c .lock .Unlock ()
if val , ok := c .frequent .Get (key ); ok {
return val , ok
}
if val , ok := c .recent .Peek (key ); ok {
c .recent .Remove (key )
c .frequent .Add (key , val )
return val , ok
}
return
}
func (c *TwoQueueCache [K , V ]) Add (key K , value V ) {
c .lock .Lock ()
defer c .lock .Unlock ()
if c .frequent .Contains (key ) {
c .frequent .Add (key , value )
return
}
if c .recent .Contains (key ) {
c .recent .Remove (key )
c .frequent .Add (key , value )
return
}
if c .recentEvict .Contains (key ) {
c .ensureSpace (true )
c .recentEvict .Remove (key )
c .frequent .Add (key , value )
return
}
c .ensureSpace (false )
c .recent .Add (key , value )
}
func (c *TwoQueueCache [K , V ]) ensureSpace (recentEvict bool ) {
recentLen := c .recent .Len ()
freqLen := c .frequent .Len ()
if recentLen +freqLen < c .size {
return
}
if recentLen > 0 && (recentLen > c .recentSize || (recentLen == c .recentSize && !recentEvict )) {
k , _ , _ := c .recent .RemoveOldest ()
c .recentEvict .Add (k , struct {}{})
return
}
c .frequent .RemoveOldest ()
}
func (c *TwoQueueCache [K , V ]) Len () int {
c .lock .RLock ()
defer c .lock .RUnlock ()
return c .recent .Len () + c .frequent .Len ()
}
func (c *TwoQueueCache [K , V ]) Resize (size int ) (evicted int ) {
c .lock .Lock ()
defer c .lock .Unlock ()
recentSize := int (float64 (size ) * c .recentRatio )
evictSize := int (float64 (size ) * c .ghostRatio )
c .size = size
c .recentSize = recentSize
diff := c .recent .Len () + c .frequent .Len () - size
if diff < 0 {
diff = 0
}
for i := 0 ; i < diff ; i ++ {
c .ensureSpace (true )
}
c .recent .Resize (size )
c .frequent .Resize (size )
c .recentEvict .Resize (evictSize )
return diff
}
func (c *TwoQueueCache [K , V ]) Keys () []K {
c .lock .RLock ()
defer c .lock .RUnlock ()
k1 := c .frequent .Keys ()
k2 := c .recent .Keys ()
return append (k1 , k2 ...)
}
func (c *TwoQueueCache [K , V ]) Values () []V {
c .lock .RLock ()
defer c .lock .RUnlock ()
v1 := c .frequent .Values ()
v2 := c .recent .Values ()
return append (v1 , v2 ...)
}
func (c *TwoQueueCache [K , V ]) Remove (key K ) {
c .lock .Lock ()
defer c .lock .Unlock ()
if c .frequent .Remove (key ) {
return
}
if c .recent .Remove (key ) {
return
}
if c .recentEvict .Remove (key ) {
return
}
}
func (c *TwoQueueCache [K , V ]) Purge () {
c .lock .Lock ()
defer c .lock .Unlock ()
c .recent .Purge ()
c .frequent .Purge ()
c .recentEvict .Purge ()
}
func (c *TwoQueueCache [K , V ]) Contains (key K ) bool {
c .lock .RLock ()
defer c .lock .RUnlock ()
return c .frequent .Contains (key ) || c .recent .Contains (key )
}
func (c *TwoQueueCache [K , V ]) Peek (key K ) (value V , ok bool ) {
c .lock .RLock ()
defer c .lock .RUnlock ()
if val , ok := c .frequent .Peek (key ); ok {
return val , ok
}
return c .recent .Peek (key )
}
The pages are generated with Golds v0.8.2 . (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 .