-
Notifications
You must be signed in to change notification settings - Fork 669
[Performance] Optimize backupFilesDict to use ConcurrentDictionary and eliminate reallocation overhead #16826
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
0035a2f
8bdbb0f
60b2819
0a1ba3b
8999be7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,4 +1,5 @@ | ||||||||||||||||||||||||||||||||||
| using System; | ||||||||||||||||||||||||||||||||||
| using System.Collections.Concurrent; | ||||||||||||||||||||||||||||||||||
| using System.Collections.Generic; | ||||||||||||||||||||||||||||||||||
| using System.Collections.ObjectModel; | ||||||||||||||||||||||||||||||||||
| using System.ComponentModel; | ||||||||||||||||||||||||||||||||||
|
|
@@ -135,7 +136,7 @@ public partial class DynamoModel : IDynamoModel, IDisposable, IEngineControllerM | |||||||||||||||||||||||||||||||||
| private readonly PathManager pathManager; | ||||||||||||||||||||||||||||||||||
| private WorkspaceModel currentWorkspace; | ||||||||||||||||||||||||||||||||||
| private Timer backupFilesTimer; | ||||||||||||||||||||||||||||||||||
| private Dictionary<Guid, string> backupFilesDict = new Dictionary<Guid, string>(); | ||||||||||||||||||||||||||||||||||
| private readonly ConcurrentDictionary<Guid, string> backupFilesDict = new ConcurrentDictionary<Guid, string>(); | ||||||||||||||||||||||||||||||||||
| internal readonly Stopwatch stopwatch = Stopwatch.StartNew(); | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| /// <summary> | ||||||||||||||||||||||||||||||||||
|
|
@@ -2685,33 +2686,43 @@ protected void SaveBackupFiles(object state) | |||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| OnRequestDispatcherBeginInvoke(() => | ||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||
| // tempDict stores the list of backup files and their corresponding workspaces IDs | ||||||||||||||||||||||||||||||||||
| // when the last auto-save operation happens. Now the IDs will be used to know | ||||||||||||||||||||||||||||||||||
| // whether some workspaces have already been backed up. If so, those workspaces won't be | ||||||||||||||||||||||||||||||||||
| // backed up again. | ||||||||||||||||||||||||||||||||||
| var tempDict = new Dictionary<Guid, string>(backupFilesDict); | ||||||||||||||||||||||||||||||||||
| backupFilesDict.Clear(); | ||||||||||||||||||||||||||||||||||
| PreferenceSettings.BackupFiles.Clear(); | ||||||||||||||||||||||||||||||||||
| // Get the current set of workspace GUIDs that need to be tracked | ||||||||||||||||||||||||||||||||||
| var currentWorkspaceGuids = new HashSet<Guid>(Workspaces.Select(w => w.Guid)); | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| // Remove entries for workspaces that no longer exist. | ||||||||||||||||||||||||||||||||||
| // ToArray() is used to materialize the collection before removal to avoid | ||||||||||||||||||||||||||||||||||
| // potential issues with collection modification during enumeration. | ||||||||||||||||||||||||||||||||||
| var guidsToRemove = backupFilesDict.Keys.Where(guid => !currentWorkspaceGuids.Contains(guid)).ToArray(); | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+2689
to
+2695
|
||||||||||||||||||||||||||||||||||
| // Get the current set of workspace GUIDs that need to be tracked | |
| var currentWorkspaceGuids = new HashSet<Guid>(Workspaces.Select(w => w.Guid)); | |
| // Remove entries for workspaces that no longer exist. | |
| // ToArray() is used to materialize the collection before removal to avoid | |
| // potential issues with collection modification during enumeration. | |
| var guidsToRemove = backupFilesDict.Keys.Where(guid => !currentWorkspaceGuids.Contains(guid)).ToArray(); | |
| // Remove entries for workspaces that no longer exist. | |
| // ToArray() is used to materialize the collection before removal to avoid | |
| // potential issues with collection modification during enumeration. | |
| var guidsToRemove = backupFilesDict.Keys | |
| .Where(guid => !Workspaces.Any(w => w.Guid == guid)) | |
| .ToArray(); |
Copilot
AI
Jan 13, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The ToArray() materializes the entire filtered collection before any removal. For large workspace sets, consider using a foreach loop directly over the filtered keys with a defensive copy (e.g., backupFilesDict.Keys.ToArray()) to avoid the intermediate Where allocation.
| // ToArray() is used to materialize the collection before removal to avoid | |
| // potential issues with collection modification during enumeration. | |
| var guidsToRemove = backupFilesDict.Keys.Where(guid => !currentWorkspaceGuids.Contains(guid)).ToArray(); | |
| foreach (var guid in guidsToRemove) | |
| { | |
| backupFilesDict.TryRemove(guid, out _); | |
| // Take a defensive snapshot of the keys to avoid issues with | |
| // collection modification during enumeration, and filter in the loop | |
| // to avoid an extra intermediate allocation. | |
| var existingGuids = backupFilesDict.Keys.ToArray(); | |
| foreach (var guid in existingGuids) | |
| { | |
| if (!currentWorkspaceGuids.Contains(guid)) | |
| { | |
| backupFilesDict.TryRemove(guid, out _); | |
| } |
Copilot
AI
Jan 13, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is a potential race condition between ContainsKey and the subsequent dictionary access. In a concurrent context with ConcurrentDictionary, another thread could remove the key between the check and use. Consider using TryGetValue instead to perform an atomic check-and-retrieve operation.
Copilot
AI
Jan 13, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using the indexer for insertion can overwrite existing entries without indication. For clarity and to match the pattern used elsewhere in this method, consider using backupFilesDict.AddOrUpdate(workspace.Guid, savePath, (key, oldValue) => savePath) or TryAdd followed by an update if needed, to make the intent explicit.
| backupFilesDict[workspace.Guid] = savePath; | |
| backupFilesDict.AddOrUpdate(workspace.Guid, savePath, (key, oldValue) => savePath); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like the dictionary should be fairly small size here containing only entries about opened workspaces. Given currently Dynamo does not allow multiple workspaces opened, seems the performance/memory impact here would be minimal.