diff --git a/.jules/bolt.md b/.jules/bolt.md index 9064926..fdf0c5f 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -85,3 +85,6 @@ ## 2024-06-15 - Optimize Dictionary allocations in C# hot loops **Learning:** In C#, LINQ `.ToDictionary` allocates a new dictionary, delegates, and enumerators. Using `.Any()` afterwards also introduces another O(N) pass. **Action:** Replace `.ToDictionary` and subsequent `.Any()` combinations with a manual `Dictionary` pre-allocated by count, populated via a `foreach` loop, and track boolean flags (e.g., `hasFiniteEdges`) inside the same loop to avoid multiple iterations and delegate allocations. +## $(date +%Y-%m-%d) - [Optimize GroupBy Redundant Enumerations] +**Learning:** In C#, applying multiple LINQ `Where()`, `ToList()`, and `Sum()` aggregations inside a `Select` projection on grouped data triggers redundant iterations over the group's elements and creates unnecessary temporary lists and delegate closures. +**Action:** Replace multiple LINQ aggregations on `IEnumerable` groupings with a single `foreach` loop that accumulates all required metrics at once. This shifts the time complexity per group from O(k*N) to strictly O(N) and drastically cuts heap allocations. diff --git a/plan.md b/plan.md index 7ae25c4..3cff352 100644 --- a/plan.md +++ b/plan.md @@ -1,19 +1,10 @@ -1. **Optimize `TemporalNodeTrafficState.Clone`:** - - Replace LINQ `.Select().Clone()` combined with `AddRange()` which causes multiple delegate/enumerator allocations. - - Use pre-sized explicit `foreach` loops to allocate clones directly into the target lists. +1. **Optimize GroupBy Redundant Enumerations in `WorkspacePresentation.cs` (Lines 8651-8678)** + - The method processes `trafficNames` utilizing multiple LINQ `Sum()` calls on `nodeStates` and `allocations` within the projection. + - Refactor this to use a standard `foreach` loop that accumulates all required metrics at once to shift time complexity from O(k*N) to O(N) and reduce heap allocations. -2. **Optimize `GetWeightedUnitCost` Signature:** - - Change the parameter `IEnumerable batches` to `List batches`. - - This avoids boxing the `List` enumerator when used in a `foreach` loop, which allocates an `IEnumerator` on the heap when passed via the interface. +2. **Complete pre-commit steps to ensure proper testing, verification, review, and reflection are done.** + - Run tests using `dotnet test`. + - Use `dotnet format` on `MedWNetworkSim.Presentation.csproj` and `MedWNetworkSim.Tests.csproj`. -3. **Optimize `ConsumeFromBatches` Sorting:** - - Replace LINQ `.OrderBy().ThenBy().ToList()` with a pre-sized `List` and `List.Sort()`. - - Because `Sequence` is globally unique via `Interlocked.Increment`, `List.Sort()` behaves deterministically as a stable sort would, avoiding O(N log N) sorting overhead generated by LINQ's multiple enumerators and allocations. - -4. **Run pre-commit steps:** - - Complete pre-commit steps to ensure proper testing, verification, review, and reflection are done. - -5. **Commit and Submit PR:** - - Run the full test suite (`dotnet test`). - - Run formatting checking (`dotnet format`). - - Create PR with title `⚡ Bolt: [performance improvement]` and the required Bolt description. +3. **Submit the changes.** + - Create a commit for the optimization with the title `⚡ Bolt: [performance improvement]` and the appropriate PR description format. diff --git a/src/MedWNetworkSim.Presentation/WorkspacePresentation.cs b/src/MedWNetworkSim.Presentation/WorkspacePresentation.cs index 5721fcd..35eb3ed 100644 --- a/src/MedWNetworkSim.Presentation/WorkspacePresentation.cs +++ b/src/MedWNetworkSim.Presentation/WorkspacePresentation.cs @@ -8658,21 +8658,47 @@ private static IReadOnlyList BuildTimelineOutcomes( .Select(pair => pair.Value) .ToList(); + // Bolt: Replaced multiple LINQ .Sum() calls with a single pass to eliminate redundant iterations and delegate allocations. + var stateAvailableSupply = 0d; + var stateDemandBacklog = 0d; + foreach (var state in nodeStates) + { + stateAvailableSupply += state.AvailableSupply; + stateDemandBacklog += state.DemandBacklog; + } + + var allocQuantity = 0d; + var allocSaleRevenue = 0d; + var allocTransportCost = 0d; + var allocProductionCost = 0d; + var allocTax = 0d; + var allocProfit = 0d; + + foreach (var allocation in allocations) + { + allocQuantity += allocation.Quantity; + allocSaleRevenue += allocation.SaleRevenue; + allocTransportCost += allocation.TotalTransportCost; + allocProductionCost += allocation.TotalProductionCost; + allocTax += allocation.TotalTax; + allocProfit += allocation.Profit; + } + return new TrafficSimulationOutcome { TrafficType = trafficType, RoutingPreference = definition?.RoutingPreference ?? allocations.FirstOrDefault()?.RoutingPreference ?? RoutingPreference.TotalCost, AllocationMode = definition?.AllocationMode ?? allocations.FirstOrDefault()?.AllocationMode ?? AllocationMode.GreedyBestRoute, - TotalProduction = nodeStates.Sum(state => state.AvailableSupply) + allocations.Sum(allocation => allocation.Quantity), - TotalConsumption = nodeStates.Sum(state => state.DemandBacklog) + allocations.Sum(allocation => allocation.Quantity), - TotalDelivered = allocations.Sum(allocation => allocation.Quantity), - UnusedSupply = nodeStates.Sum(state => state.AvailableSupply), - UnmetDemand = nodeStates.Sum(state => state.DemandBacklog), - TotalSalesRevenue = allocations.Sum(allocation => allocation.SaleRevenue), - TotalTransportCost = allocations.Sum(allocation => allocation.TotalTransportCost), - TotalProductionCost = allocations.Sum(allocation => allocation.TotalProductionCost), - TotalTax = allocations.Sum(allocation => allocation.TotalTax), - TotalProfit = allocations.Sum(allocation => allocation.Profit), + TotalProduction = stateAvailableSupply + allocQuantity, + TotalConsumption = stateDemandBacklog + allocQuantity, + TotalDelivered = allocQuantity, + UnusedSupply = stateAvailableSupply, + UnmetDemand = stateDemandBacklog, + TotalSalesRevenue = allocSaleRevenue, + TotalTransportCost = allocTransportCost, + TotalProductionCost = allocProductionCost, + TotalTax = allocTax, + TotalProfit = allocProfit, Allocations = allocations }; }).ToList();