Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,16 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

## [0.48.29] - 2026-05-18

### Changed
- **IconExtractorService** — `FindExecutableByName` results are now cached in a
`ConcurrentDictionary`, eliminating repeated Program Files directory scans
(~100+ subdirs) on every process list refresh (PERF-M5).
- **NetworkSharedState** — `TrimBuffer` now uses a clear-and-rebuild strategy
when removing more than 25% of buffer entries, reducing worst-case complexity
from O(n×removeCount) to O(n) (PERF-M3).

## [0.48.28] - 2026-05-18

### Fixed
Expand Down
9 changes: 9 additions & 0 deletions SysManager/SysManager/Services/IconExtractorService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -358,11 +358,20 @@ internal static string NormalizePath(string raw)
return null;
}

// PERF-M5: Cache resolved executable paths to avoid re-scanning Program Files
// directories on every process list refresh (~100+ subdirs per scan).
private static readonly ConcurrentDictionary<string, string?> _pathCache = new(StringComparer.OrdinalIgnoreCase);

private static string? FindExecutableByName(string processName)
{
var exeName = processName.EndsWith(".exe", StringComparison.OrdinalIgnoreCase)
? processName : processName + ".exe";

return _pathCache.GetOrAdd(exeName, static name => ResolveExecutablePath(name));
}

private static string? ResolveExecutablePath(string exeName)
{
var found = SearchInPath(exeName);
if (found != null) return found;

Expand Down
26 changes: 21 additions & 5 deletions SysManager/SysManager/ViewModels/NetworkSharedState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -472,11 +472,27 @@ internal void TrimBuffer(ObservableCollection<DateTimePoint> buffer)
while (removeCount < buffer.Count && buffer[removeCount].DateTime < cutoff)
removeCount++;

// PERF-003: Remove stale items from the front. Removing from index 0
// repeatedly is O(n*removeCount) but unavoidable with ObservableCollection.
// We remove forward (always index 0) which is simpler and equivalent.
for (int i = 0; i < removeCount; i++)
buffer.RemoveAt(0);
if (removeCount == 0) return;

// PERF-M3: When removing many items from the front, repeated RemoveAt(0)
// is O(n*removeCount) because each removal shifts all remaining elements.
// If we're removing more than 25% of the buffer, it's cheaper to snapshot
// the items we want to keep, clear, and re-add them (O(n) total).
if (removeCount > buffer.Count / 4)
{
var keep = new DateTimePoint[buffer.Count - removeCount];
for (int i = 0; i < keep.Length; i++)
keep[i] = buffer[removeCount + i];
buffer.Clear();
foreach (var item in keep)
buffer.Add(item);
}
else
{
// Small number of removals — RemoveAt(0) is acceptable.
for (int i = 0; i < removeCount; i++)
buffer.RemoveAt(0);
}
}

internal void TrimAllBuffers()
Expand Down
Loading