From 3b639a814240eb6065507814eefc773e246b60fe Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 22 May 2026 17:29:38 +0000 Subject: [PATCH] =?UTF-8?q?=E2=9A=A1=20Bolt:=20Optimize=20O(N^2)=20timelin?= =?UTF-8?q?e.NodeStates=20lookup=20inside=20ApplySimulationOutcomes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .jules/bolt.md | 3 +++ .../WorkspacePresentation.cs | 21 ++++++++++++------- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/.jules/bolt.md b/.jules/bolt.md index 7e209ff..10dec3e 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -23,3 +23,6 @@ ## 2024-05-23 - Optimize LINQ Allocations in Network Simulation Engine **Learning:** Found multiple places in `NetworkSimulationEngine.cs` where LINQ `.Count()` and `.Select().Min()` inside inner loops on the simulation path were allocating delegates and enumerators on the hot path (e.g., inside `GetPathRemainingCapacity` and `CountBottleneckResources`). **Action:** Replaced these LINQ chains with `for` and `foreach` loops respectively to minimize garbage generation during greedy route allocation, reducing allocations during repeated graph evaluations. +## 2026-05-22 - O(N^2) Lookup inside Simulation Updates +**Learning:** In the Avalonia presentation layer, `ApplySimulationOutcomes` in `WorkspacePresentation` was performing an O(N^2) nested lookup by filtering `timeline.NodeStates` inside a loop that iterated over every node in the scene. For large networks, this repeated filtering could freeze the UI during heavy simulation updates. +**Action:** Pre-compute node states into a grouped lookup dictionary outside of the loop, reducing the update bottleneck to O(N + S). diff --git a/src/MedWNetworkSim.Presentation/WorkspacePresentation.cs b/src/MedWNetworkSim.Presentation/WorkspacePresentation.cs index 980ca43..8439dde 100644 --- a/src/MedWNetworkSim.Presentation/WorkspacePresentation.cs +++ b/src/MedWNetworkSim.Presentation/WorkspacePresentation.cs @@ -8797,23 +8797,30 @@ private void ApplySimulationOutcomes(IEnumerable allocations, T var nodesById = network.Nodes.ToDictionary(node => node.Id, Comparer); if (timeline is not null) { + // Bolt: Pre-compute node states to optimize O(N^2) timeline.NodeStates lookup + var nodeStatesByNodeId = timeline.NodeStates + .GroupBy(pair => pair.Key.NodeId, Comparer) + .ToDictionary(group => group.Key, group => group.ToList(), Comparer); + foreach (var node in Scene.Nodes) { if (!nodesById.TryGetValue(node.Id, out var nodeModel)) continue; - var state = timeline.NodeStates - .Where(pair => Comparer.Equals(pair.Key.NodeId, node.Id)) - .Select(pair => pair.Value) - .FirstOrDefault(); - var backlogByTraffic = timeline.NodeStates - .Where(pair => Comparer.Equals(pair.Key.NodeId, node.Id) && pair.Value.DemandBacklog > 0d) + + var nodeTrafficStates = nodeStatesByNodeId.GetValueOrDefault(node.Id) ?? []; + + var state = nodeTrafficStates.Select(pair => pair.Value).FirstOrDefault(); + + var backlogByTraffic = nodeTrafficStates + .Where(pair => pair.Value.DemandBacklog > 0d) .GroupBy(pair => pair.Key.TrafficType, pair => pair.Value.DemandBacklog, Comparer) .Select(group => new KeyValuePair(group.Key, group.Sum())) .ToList(); + var pressure = timeline.NodePressureById.GetValueOrDefault(node.Id); node.MetricsLabel = string.Empty; node.DetailLines = BuildNodeDetailLines(nodeModel, backlogByTraffic, pressure.Score > 0d ? pressure : null); UpdateSceneNodeLayout(node, nodeModel, pressure.Score > 0d ? pressure : null, graphRenderer.GetZoomTier(Viewport.Zoom)); - node.HasWarning = pressure.Score > 0d || state.DemandBacklog > 0d; + node.HasWarning = pressure.Score > 0d || (state.DemandBacklog > 0d); } } else