Skip to content
Open
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
3 changes: 3 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
## 2026-07-03 - Avoid LINQ multiple enumerator allocations during complex aggregations
**Learning:** In C#, applying `.GroupBy()` followed by `.ToDictionary()` that encapsulates `.Sum()` inside the value projection allocates unnecessary delegates and sequence enumerators, causing GC pressure on hot paths.
**Action:** Replace `GroupBy` and `ToDictionary` combinations with a manually pre-sized `Dictionary` and standard `foreach` loops to combine item insertion and value aggregation simultaneously. This prevents sequence allocations, delegate closures, and multiple O(N) enumeration passes.
52 changes: 31 additions & 21 deletions src/MedWNetworkSim.App/Services/NetworkSimulationEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,19 @@ public IReadOnlyList<TrafficSimulationOutcome> Simulate(NetworkModel network)
}

var hasRecipeDependencies = HasStaticRecipeDependencies(network);
var definitionsByTraffic = network.TrafficTypes
.Where(definition => !string.IsNullOrWhiteSpace(definition.Name))
.GroupBy(definition => definition.Name, Comparer)
.ToDictionary(group => group.Key, group => group.First(), Comparer);

// Bolt: Replaced LINQ Where, GroupBy, and ToDictionary with a manual dictionary setup to avoid O(N) allocation overhead.
var definitionsByTraffic = new Dictionary<string, TrafficTypeDefinition>(Comparer);
foreach (var definition in network.TrafficTypes)
{
if (string.IsNullOrWhiteSpace(definition.Name)) continue;
// .GroupBy(key).First() is equivalent to adding only if the key does not already exist.
if (!definitionsByTraffic.ContainsKey(definition.Name))
{
definitionsByTraffic[definition.Name] = definition;
}
}

var contexts = MixedRoutingAllocator.BuildStaticContexts(network, applyLocalAllocations: !hasRecipeDependencies).ToList();
// Bolt: Replaced LINQ .ToDictionary() with manual foreach to avoid enumerator and delegate allocations
var remainingCapacityByEdgeId = new Dictionary<string, double>(network.Edges.Count, Comparer);
Expand Down Expand Up @@ -758,24 +767,25 @@ private static double ResolveBaseProductionCost(NodeTrafficProfile? profile, Tra

private static Dictionary<string, double> SummarizeLandedUnitCosts(IEnumerable<RouteAllocation> allocations)
{
return allocations
.GroupBy(allocation => allocation.ConsumerNodeId, Comparer)
.ToDictionary(
group => group.Key,
group =>
{
// Bolt: Accumulate quantity and total cost in a single loop to avoid multiple O(N) LINQ enumerations and delegate allocations
var quantity = 0d;
var totalCost = 0d;
foreach (var allocation in group)
{
quantity += allocation.Quantity;
totalCost += allocation.DeliveredCostPerUnit * allocation.Quantity;
}
// Bolt: Replaced LINQ GroupBy and ToDictionary with a manual loop to prevent sequence allocations and delegate closures
var tempStorage = new Dictionary<string, (double quantity, double totalCost)>(Comparer);
foreach (var allocation in allocations)
{
if (!tempStorage.TryGetValue(allocation.ConsumerNodeId, out var current))
{
current = (0d, 0d);
}
current.quantity += allocation.Quantity;
current.totalCost += allocation.DeliveredCostPerUnit * allocation.Quantity;
tempStorage[allocation.ConsumerNodeId] = current;
}

return quantity > Epsilon ? totalCost / quantity : 0d;
},
Comparer);
var result = new Dictionary<string, double>(tempStorage.Count, Comparer);
foreach (var pair in tempStorage)
{
result[pair.Key] = pair.Value.quantity > Epsilon ? pair.Value.totalCost / pair.Value.quantity : 0d;
}
return result;
}

private static TrafficContext BuildContext(NetworkModel network, TrafficTypeDefinition definition)
Expand Down
Loading