From fd0591281b7f48a3dff9f8e995831d6eda3ddf7b Mon Sep 17 00:00:00 2001 From: Hongtao Zhang Date: Mon, 22 Jan 2024 12:06:14 -0600 Subject: [PATCH 1/3] Use FastCache.Cached as new image cache instead of implement it ourselves --- .../Flow.Launcher.Infrastructure.csproj | 1 + .../Image/ImageCache.cs | 78 +++++++------------ .../Image/ImageLoader.cs | 11 ++- 3 files changed, 38 insertions(+), 52 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj b/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj index b24f069c1ba..574f3686a6d 100644 --- a/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj +++ b/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj @@ -49,6 +49,7 @@ + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Flow.Launcher.Infrastructure/Image/ImageCache.cs b/Flow.Launcher.Infrastructure/Image/ImageCache.cs index 7a2b5763756..55545b9a732 100644 --- a/Flow.Launcher.Infrastructure/Image/ImageCache.cs +++ b/Flow.Launcher.Infrastructure/Image/ImageCache.cs @@ -4,13 +4,13 @@ using System.Linq; using System.Threading; using System.Windows.Media; +using FastCache; +using FastCache.Services; namespace Flow.Launcher.Infrastructure.Image { - [Serializable] public class ImageUsage { - public int usage; public ImageSource imageSource; @@ -23,16 +23,13 @@ public ImageUsage(int usage, ImageSource image) public class ImageCache { - private const int MaxCached = 50; - public ConcurrentDictionary<(string, bool), ImageUsage> Data { get; } = new(); - private const int permissibleFactor = 2; - private SemaphoreSlim semaphore = new(1, 1); + private const int MaxCached = 150; public void Initialize(Dictionary<(string, bool), int> usage) { foreach (var key in usage.Keys) { - Data[key] = new ImageUsage(usage[key], null); + Cached.Save(key, new ImageUsage(usage[key], null), TimeSpan.MaxValue, MaxCached); } } @@ -40,70 +37,48 @@ public void Initialize(Dictionary<(string, bool), int> usage) { get { - if (!Data.TryGetValue((path, isFullImage), out var value)) + if (!Cached.TryGet((path, isFullImage), out var value)) { return null; } - value.usage++; - return value.imageSource; + value.Value.usage++; + return value.Value.imageSource; } set { - Data.AddOrUpdate( - (path, isFullImage), - new ImageUsage(0, value), - (k, v) => - { - v.imageSource = value; - v.usage++; - return v; - } - ); - - SliceExtra(); - - async void SliceExtra() + if (Cached.TryGet((path, isFullImage), out var cached)) { - // To prevent the dictionary from drastically increasing in size by caching images, the dictionary size is not allowed to grow more than the permissibleFactor * maxCached size - // This is done so that we don't constantly perform this resizing operation and also maintain the image cache size at the same time - if (Data.Count > permissibleFactor * MaxCached) - { - await semaphore.WaitAsync().ConfigureAwait(false); - // To delete the images from the data dictionary based on the resizing of the Usage Dictionary - // Double Check to avoid concurrent remove - if (Data.Count > permissibleFactor * MaxCached) - foreach (var key in Data.OrderBy(x => x.Value.usage).Take(Data.Count - MaxCached).Select(x => x.Key)) - Data.TryRemove(key, out _); - semaphore.Release(); - } + cached.Value.imageSource = value; + cached.Value.usage++; } + + Cached.Save((path, isFullImage), new ImageUsage(0, value), TimeSpan.MaxValue, + MaxCached); } } public bool ContainsKey(string key, bool isFullImage) { - return key is not null && Data.ContainsKey((key, isFullImage)) && Data[(key, isFullImage)].imageSource != null; + return Cached.TryGet((key, isFullImage), out _); } public bool TryGetValue(string key, bool isFullImage, out ImageSource image) { - if (key is not null) - { - bool hasKey = Data.TryGetValue((key, isFullImage), out var imageUsage); - image = hasKey ? imageUsage.imageSource : null; - return hasKey; - } - else + if (Cached.TryGet((key, isFullImage), out var value)) { - image = null; - return false; + image = value.Value.imageSource; + value.Value.usage++; + return image != null; } + + image = null; + return false; } public int CacheSize() { - return Data.Count; + return CacheManager.TotalCount<(string, bool), ImageUsage>(); } /// @@ -111,7 +86,14 @@ public int CacheSize() /// public int UniqueImagesInCache() { - return Data.Values.Select(x => x.imageSource).Distinct().Count(); + return CacheManager.EnumerateEntries<(string, bool), ImageUsage>().Select(x => x.Value.imageSource) + .Distinct() + .Count(); + } + + public IEnumerable> EnumerateEntries() + { + return CacheManager.EnumerateEntries<(string, bool), ImageUsage>(); } } } diff --git a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs index add6d4e9237..75c2a4ec989 100644 --- a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs +++ b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs @@ -49,7 +49,7 @@ public static async Task InitializeAsync() { await Stopwatch.NormalAsync("|ImageLoader.Initialize|Preload images cost", async () => { - foreach (var ((path, isFullImage), _) in ImageCache.Data) + foreach (var ((path, isFullImage), _) in usage) { await LoadAsync(path, isFullImage); } @@ -65,7 +65,7 @@ public static async Task Save() try { - _storage.SaveAsync(ImageCache.Data + await _storage.SaveAsync(ImageCache.EnumerateEntries() .ToDictionary( x => x.Key, x => x.Value.usage)); @@ -125,9 +125,12 @@ private static async ValueTask LoadInternalAsync(string path, bool return new ImageResult(MissingImage, ImageType.Error); } - if (ImageCache.ContainsKey(path, loadFullImage)) + // extra scope for use of same variable name { - return new ImageResult(ImageCache[path, loadFullImage], ImageType.Cache); + if (ImageCache.TryGetValue(path, loadFullImage, out var imageSource)) + { + return new ImageResult(imageSource, ImageType.Cache); + } } if (Uri.TryCreate(path, UriKind.RelativeOrAbsolute, out var uriResult) From 3574dba0461b452448d43a7d05b79e3497f34851 Mon Sep 17 00:00:00 2001 From: Hongtao Zhang Date: Wed, 29 May 2024 17:57:26 -0500 Subject: [PATCH 2/3] Change Cache to LFU --- .../Flow.Launcher.Infrastructure.csproj | 2 +- .../Image/ImageCache.cs | 66 +++++++------------ .../Image/ImageLoader.cs | 20 +++--- 3 files changed, 35 insertions(+), 53 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj b/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj index e36b9c5e03b..7d6448c43b6 100644 --- a/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj +++ b/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj @@ -49,7 +49,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Flow.Launcher.Infrastructure/Image/ImageCache.cs b/Flow.Launcher.Infrastructure/Image/ImageCache.cs index 55545b9a732..1b0fb60eac2 100644 --- a/Flow.Launcher.Infrastructure/Image/ImageCache.cs +++ b/Flow.Launcher.Infrastructure/Image/ImageCache.cs @@ -3,33 +3,23 @@ using System.Collections.Generic; using System.Linq; using System.Threading; +using System.Threading.Tasks; using System.Windows.Media; -using FastCache; -using FastCache.Services; +using BitFaster.Caching.Lfu; namespace Flow.Launcher.Infrastructure.Image { - public class ImageUsage - { - public int usage; - public ImageSource imageSource; - - public ImageUsage(int usage, ImageSource image) - { - this.usage = usage; - imageSource = image; - } - } - public class ImageCache { - private const int MaxCached = 150; + private const int MaxCached = 10; + + private ConcurrentLfu<(string, bool), ImageSource> CacheManager { get; set; } = new(MaxCached); - public void Initialize(Dictionary<(string, bool), int> usage) + public void Initialize(IEnumerable<(string, bool)> usage) { - foreach (var key in usage.Keys) + foreach (var key in usage) { - Cached.Save(key, new ImageUsage(usage[key], null), TimeSpan.MaxValue, MaxCached); + CacheManager.AddOrUpdate(key, null); } } @@ -37,48 +27,42 @@ public void Initialize(Dictionary<(string, bool), int> usage) { get { - if (!Cached.TryGet((path, isFullImage), out var value)) - { - return null; - } - - value.Value.usage++; - return value.Value.imageSource; + return CacheManager.TryGet((path, isFullImage), out var value) ? value : null; } set { - if (Cached.TryGet((path, isFullImage), out var cached)) - { - cached.Value.imageSource = value; - cached.Value.usage++; - } - - Cached.Save((path, isFullImage), new ImageUsage(0, value), TimeSpan.MaxValue, - MaxCached); + CacheManager.AddOrUpdate((path, isFullImage), value); } } + public async ValueTask GetOrAddAsync(string key, + Func<(string, bool), Task> valueFactory, + bool isFullImage = false) + { + return await CacheManager.GetOrAddAsync((key, isFullImage), valueFactory); + } + public bool ContainsKey(string key, bool isFullImage) { - return Cached.TryGet((key, isFullImage), out _); + return CacheManager.TryGet((key, isFullImage), out _); } public bool TryGetValue(string key, bool isFullImage, out ImageSource image) { - if (Cached.TryGet((key, isFullImage), out var value)) + if (CacheManager.TryGet((key, isFullImage), out var value)) { - image = value.Value.imageSource; - value.Value.usage++; + image = value; return image != null; } + image = null; return false; } public int CacheSize() { - return CacheManager.TotalCount<(string, bool), ImageUsage>(); + return CacheManager.Count; } /// @@ -86,14 +70,14 @@ public int CacheSize() /// public int UniqueImagesInCache() { - return CacheManager.EnumerateEntries<(string, bool), ImageUsage>().Select(x => x.Value.imageSource) + return CacheManager.Select(x => x.Value) .Distinct() .Count(); } - public IEnumerable> EnumerateEntries() + public IEnumerable> EnumerateEntries() { - return CacheManager.EnumerateEntries<(string, bool), ImageUsage>(); + return CacheManager; } } } diff --git a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs index 75c2a4ec989..612f495be64 100644 --- a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs +++ b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using System.Windows.Documents; using System.Windows.Media; using System.Windows.Media.Imaging; using Flow.Launcher.Infrastructure.Logger; @@ -17,7 +18,7 @@ public static class ImageLoader { private static readonly ImageCache ImageCache = new(); private static SemaphoreSlim storageLock { get; } = new SemaphoreSlim(1, 1); - private static BinaryStorage> _storage; + private static BinaryStorage> _storage; private static readonly ConcurrentDictionary GuidToKey = new(); private static IImageHashGenerator _hashGenerator; private static readonly bool EnableImageHash = true; @@ -31,12 +32,12 @@ public static class ImageLoader public static async Task InitializeAsync() { - _storage = new BinaryStorage>("Image"); + _storage = new BinaryStorage>("Image"); _hashGenerator = new ImageHashGenerator(); var usage = await LoadStorageToConcurrentDictionaryAsync(); - ImageCache.Initialize(usage.ToDictionary(x => x.Key, x => x.Value)); + ImageCache.Initialize(usage); foreach (var icon in new[] { Constant.DefaultIcon, Constant.MissingImgIcon }) { @@ -49,7 +50,7 @@ public static async Task InitializeAsync() { await Stopwatch.NormalAsync("|ImageLoader.Initialize|Preload images cost", async () => { - foreach (var ((path, isFullImage), _) in usage) + foreach (var (path, isFullImage) in usage) { await LoadAsync(path, isFullImage); } @@ -66,9 +67,8 @@ public static async Task Save() try { await _storage.SaveAsync(ImageCache.EnumerateEntries() - .ToDictionary( - x => x.Key, - x => x.Value.usage)); + .Select(x => x.Key) + .ToList()); } finally { @@ -76,14 +76,12 @@ await _storage.SaveAsync(ImageCache.EnumerateEntries() } } - private static async Task> LoadStorageToConcurrentDictionaryAsync() + private static async Task> LoadStorageToConcurrentDictionaryAsync() { await storageLock.WaitAsync(); try { - var loaded = await _storage.TryLoadAsync(new Dictionary<(string, bool), int>()); - - return new ConcurrentDictionary<(string, bool), int>(loaded); + return await _storage.TryLoadAsync(new List<(string, bool)>()); } finally { From 9ff81adcc3bc884c2580ddbd83b8ab6f3f7a16df Mon Sep 17 00:00:00 2001 From: DB p Date: Thu, 30 May 2024 14:48:56 +0900 Subject: [PATCH 3/3] Fix MaxCahced count --- Flow.Launcher.Infrastructure/Image/ImageCache.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher.Infrastructure/Image/ImageCache.cs b/Flow.Launcher.Infrastructure/Image/ImageCache.cs index 1b0fb60eac2..ddbab4ef0b1 100644 --- a/Flow.Launcher.Infrastructure/Image/ImageCache.cs +++ b/Flow.Launcher.Infrastructure/Image/ImageCache.cs @@ -11,7 +11,7 @@ namespace Flow.Launcher.Infrastructure.Image { public class ImageCache { - private const int MaxCached = 10; + private const int MaxCached = 150; private ConcurrentLfu<(string, bool), ImageSource> CacheManager { get; set; } = new(MaxCached);