From 6f53463c31bb42a10ac0f93551c4d7a189459c9b Mon Sep 17 00:00:00 2001 From: Nick Craig-Wood Date: Tue, 5 Apr 2022 10:29:39 +0100 Subject: [PATCH] vfs: implement LRU-SP cache for more intelligent cache replacement - FIXME WIP FIXME this needs docs, and maybe needs to be configurable. FIXME needs tests also See: https://forum.rclone.org/t/rclone-vfs-cache-maximum-size-per-file/30037 Fixes: #4110 --- vfs/vfscache/cache.go | 4 ++++ vfs/vfscache/item.go | 33 ++++++++++++++++++++++++++++++++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/vfs/vfscache/cache.go b/vfs/vfscache/cache.go index 164950388..8759c43ff 100644 --- a/vfs/vfscache/cache.go +++ b/vfs/vfscache/cache.go @@ -618,9 +618,11 @@ func (c *Cache) purgeClean(quota int64) { } // Make a slice of clean cache files + now := time.Now() for _, item := range c.item { if !item.IsDirty() { items = append(items, item) + item.updateScore(now) } } @@ -709,9 +711,11 @@ func (c *Cache) purgeOverQuota(quota int64) { var items Items // Make a slice of unused files + now := time.Now() for _, item := range c.item { if !item.inUse() { items = append(items, item) + item.updateScore(now) } } diff --git a/vfs/vfscache/item.go b/vfs/vfscache/item.go index e1575eca3..1ea3ce69f 100644 --- a/vfs/vfscache/item.go +++ b/vfs/vfscache/item.go @@ -59,6 +59,7 @@ type Item struct { mu sync.Mutex // protect the variables cond sync.Cond // synchronize with cache cleaner name string // name in the VFS + score float64 // score for likelihood of reaping from cache (bigger is more likely) opens int // number of times file is open downloaders *downloaders.Downloaders // a record of the downloaders in action - may be nil o fs.Object // object we are caching - may be nil @@ -75,6 +76,7 @@ type Info struct { ModTime time.Time // last time file was modified ATime time.Time // last time file was accessed Size int64 // size of the file + Opens int64 // number of times the file has been opened Rs ranges.Ranges // which parts of the file are present Fingerprint string // fingerprint of remote object Dirty bool // set if the backing file has been modified @@ -103,6 +105,8 @@ func (rr ResetResult) String() string { func (v Items) Len() int { return len(v) } func (v Items) Swap(i, j int) { v[i], v[j] = v[j], v[i] } + +// Less implements the caching strategy of the VFS cache. func (v Items) Less(i, j int) bool { if i == j { return false @@ -114,7 +118,7 @@ func (v Items) Less(i, j int) bool { jItem.mu.Lock() defer jItem.mu.Unlock() - return iItem.info.ATime.Before(jItem.info.ATime) + return iItem.score > jItem.score } // clean the item after its cache file has been deleted @@ -179,6 +183,32 @@ func (item *Item) getATime() time.Time { return item.info.ATime } +// update the score for the item and return it +// +// Bigger scores mean more likely to be reaped +func (item *Item) updateScore(now time.Time) float64 { + item.mu.Lock() + defer item.mu.Unlock() + accessedAgo := now.Sub(item.info.ATime).Seconds() + + // For LRU cache, score is just how long ago it was accessed + // item.score = accessedAgo + + // For LRU-SP cache score is size * accessedAgo / opens + opens := float64(item.opens) + if opens <= 1 { + opens = 1 + } + size := float64(item.info.Rs.Size()) + if size < 4096 { + size = 4096 // minimum size is 1 disk block ish + + } + item.score = size * accessedAgo / opens + + return item.score +} + // getDiskSize returns the size on disk (approximately) of the item // // We return the sizes of the chunks we have fetched, however there is @@ -526,6 +556,7 @@ func (item *Item) open(o fs.Object) (err error) { defer item.mu.Unlock() item.info.ATime = time.Now() + item.info.Opens++ osPath, err := item.c.createItemDir(item.name) // No locking in Cache if err != nil {