From 0b1ddd39d500c39103d7fc68d23e62b74067242b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Domeradzki?= Date: Sun, 30 Mar 2025 21:06:33 +0200 Subject: [PATCH] Optimize serializable file writes --- .../Collections/ConcurrentHashSet.cs | 81 ++++++++++++------- ArchiSteamFarm/Collections/ConcurrentList.cs | 19 ++++- .../ObservableConcurrentDictionary.cs | 2 + ArchiSteamFarm/Helpers/SerializableFile.cs | 12 --- 4 files changed, 73 insertions(+), 41 deletions(-) diff --git a/ArchiSteamFarm/Collections/ConcurrentHashSet.cs b/ArchiSteamFarm/Collections/ConcurrentHashSet.cs index cadce40d4..232d59b0c 100644 --- a/ArchiSteamFarm/Collections/ConcurrentHashSet.cs +++ b/ArchiSteamFarm/Collections/ConcurrentHashSet.cs @@ -100,9 +100,7 @@ public sealed class ConcurrentHashSet : IReadOnlySet, ISet where T : no public void ExceptWith(IEnumerable other) { ArgumentNullException.ThrowIfNull(other); - foreach (T item in other) { - Remove(item); - } + RemoveRange(other); } [MustDisposeResource] @@ -113,8 +111,14 @@ public sealed class ConcurrentHashSet : IReadOnlySet, ISet where T : no IReadOnlySet otherSet = other as IReadOnlySet ?? other.ToHashSet(); - foreach (T item in this.Where(item => !otherSet.Contains(item))) { - Remove(item); + bool modified = false; + + foreach (T _ in this.Where(item => !otherSet.Contains(item) && BackingCollection.TryRemove(item, out _))) { + modified = true; + } + + if (modified) { + OnModified?.Invoke(this, EventArgs.Empty); } } @@ -182,24 +186,24 @@ public sealed class ConcurrentHashSet : IReadOnlySet, ISet where T : no ArgumentNullException.ThrowIfNull(other); IReadOnlySet otherSet = other as IReadOnlySet ?? other.ToHashSet(); - HashSet removed = []; - foreach (T item in otherSet.Where(Contains)) { - removed.Add(item); - Remove(item); + HashSet removed = otherSet.Where(item => Contains(item) && BackingCollection.TryRemove(item, out _)).ToHashSet(); + + bool modified = removed.Count > 0; + + foreach (T _ in otherSet.Where(item => !removed.Contains(item) && BackingCollection.TryAdd(item, true))) { + modified = true; } - foreach (T item in otherSet.Where(item => !removed.Contains(item))) { - Add(item); + if (modified) { + OnModified?.Invoke(this, EventArgs.Empty); } } public void UnionWith(IEnumerable other) { ArgumentNullException.ThrowIfNull(other); - foreach (T otherElement in other) { - Add(otherElement); - } + AddRange(other); } void ICollection.Add(T item) { @@ -215,44 +219,66 @@ public sealed class ConcurrentHashSet : IReadOnlySet, ISet where T : no public bool AddRange(IEnumerable items) { ArgumentNullException.ThrowIfNull(items); - bool result = false; + bool modified = false; - foreach (T _ in items.Where(Add)) { - result = true; + foreach (T _ in items.Where(item => BackingCollection.TryAdd(item, true))) { + modified = true; } - return result; + if (modified) { + OnModified?.Invoke(this, EventArgs.Empty); + } + + return modified; } [PublicAPI] public bool RemoveRange(IEnumerable items) { ArgumentNullException.ThrowIfNull(items); - bool result = false; + bool modified = false; - foreach (T _ in items.Where(Remove)) { - result = true; + foreach (T _ in items.Where(item => BackingCollection.TryRemove(item, out _))) { + modified = true; } - return result; + if (modified) { + OnModified?.Invoke(this, EventArgs.Empty); + } + + return modified; } [PublicAPI] public int RemoveWhere(Predicate match) { ArgumentNullException.ThrowIfNull(match); - return BackingCollection.Keys.Where(match.Invoke).Count(key => BackingCollection.TryRemove(key, out _)); + int count = BackingCollection.Keys.Where(match.Invoke).Count(key => BackingCollection.TryRemove(key, out _)); + + if (count > 0) { + OnModified?.Invoke(this, EventArgs.Empty); + } + + return count; } [PublicAPI] - public bool ReplaceIfNeededWith(IReadOnlyCollection other) { + public bool ReplaceIfNeededWith(IEnumerable other) { ArgumentNullException.ThrowIfNull(other); - if (SetEquals(other)) { + ICollection otherCollection = other as ICollection ?? other.ToHashSet(); + + if (SetEquals(otherCollection)) { return false; } - ReplaceWith(other); + BackingCollection.Clear(); + + foreach (T item in otherCollection) { + BackingCollection.TryAdd(item, true); + } + + OnModified?.Invoke(this, EventArgs.Empty); return true; } @@ -261,7 +287,6 @@ public sealed class ConcurrentHashSet : IReadOnlySet, ISet where T : no public void ReplaceWith(IEnumerable other) { ArgumentNullException.ThrowIfNull(other); - Clear(); - UnionWith(other); + ReplaceIfNeededWith(other); } } diff --git a/ArchiSteamFarm/Collections/ConcurrentList.cs b/ArchiSteamFarm/Collections/ConcurrentList.cs index a25b70afd..d83667540 100644 --- a/ArchiSteamFarm/Collections/ConcurrentList.cs +++ b/ArchiSteamFarm/Collections/ConcurrentList.cs @@ -24,6 +24,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Linq; using System.Text.Json.Serialization; using JetBrains.Annotations; using Nito.AsyncEx; @@ -60,6 +61,12 @@ public sealed class ConcurrentList : IList, IReadOnlyList { ArgumentOutOfRangeException.ThrowIfNegative(index); using (Lock.WriterLock()) { + T oldValue = BackingCollection[index]; + + if (EqualityComparer.Default.Equals(oldValue, value)) { + return; + } + BackingCollection[index] = value; } @@ -86,6 +93,10 @@ public sealed class ConcurrentList : IList, IReadOnlyList { public void Clear() { using (Lock.WriterLock()) { + if (BackingCollection.Count == 0) { + return; + } + BackingCollection.Clear(); } @@ -155,9 +166,15 @@ public sealed class ConcurrentList : IList, IReadOnlyList { public void ReplaceWith(IEnumerable collection) { ArgumentNullException.ThrowIfNull(collection); + ICollection newCollection = collection as ICollection ?? collection.ToList(); + using (Lock.WriterLock()) { + if (BackingCollection.SequenceEqual(newCollection)) { + return; + } + BackingCollection.Clear(); - BackingCollection.AddRange(collection); + BackingCollection.AddRange(newCollection); } OnModified?.Invoke(this, EventArgs.Empty); diff --git a/ArchiSteamFarm/Collections/ObservableConcurrentDictionary.cs b/ArchiSteamFarm/Collections/ObservableConcurrentDictionary.cs index 2feef2d3b..c0e83ee51 100644 --- a/ArchiSteamFarm/Collections/ObservableConcurrentDictionary.cs +++ b/ArchiSteamFarm/Collections/ObservableConcurrentDictionary.cs @@ -65,6 +65,7 @@ public sealed class ObservableConcurrentDictionary : IDictionary : IDictionary