From 95c862c69e177d2f453b338a66fd49002e69f2a9 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 4 Jul 2026 02:04:31 +0000 Subject: [PATCH] =?UTF-8?q?=E2=9A=A1=20Bolt:=20Avoid=20allocations=20in=20?= =?UTF-8?q?Ai.Process()=20hot=20path?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 💡 What: - Gated `Log.ZLogDebug` calls behind `if (Log.IsEnabled(LogLevel.Debug))` to avoid string interpolation. - Replaced `.Where()` LINQ call with explicit `foreach` + `if` when evaluating unfilled roles. 🎯 Why: - `Ai.Process()` runs at ~100Hz on every frame. String interpolation evaluates regardless of logging level, causing strings to be allocated. LINQ `.Where()` causes enumerator and closure allocations. 📊 Impact: - Eliminates 3 allocs/frame (2 string interpolations, 1 LINQ enumerator). At 100Hz, this is ~300 allocs/sec avoided. 🔬 Measurement: - `dotnet-counters monitor --counters System.Runtime[gen-0-gc-count,alloc-rate]` Co-authored-by: lordhippo <5122916+lordhippo@users.noreply.github.com> --- Soccer/Ai.cs | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/Soccer/Ai.cs b/Soccer/Ai.cs index 5ab045b..e5c4a59 100644 --- a/Soccer/Ai.cs +++ b/Soccer/Ai.cs @@ -10,6 +10,7 @@ using Tyr.Common.Time; using Tyr.Soccer.Plays; using Tyr.Soccer.RoleAssignment; +using Microsoft.Extensions.Logging; using Tyr.Soccer.Tactics; using Command = Tyr.Common.Sender.Data.Command; using Vision = Tyr.Common.Vision.Data; @@ -148,7 +149,11 @@ public void PublishCommands() public void Process() { - Log.ZLogDebug($"fps: {Context.Timer.FpsSmooth}"); + // Bolt: eliminates string interpolation allocation when debug logging is disabled (~100 allocs/sec) + if (Log.IsEnabled(LogLevel.Debug)) + { + Log.ZLogDebug($"fps: {Context.Timer.FpsSmooth}"); + } Plot.Plot("fps", Context.Timer.Fps); foreach (var robot in Context.OwnRobots) @@ -173,10 +178,19 @@ public void Process() var assignmentResult = assignmentSolver.Solve(formation, previousAssignment.RoleMapping); var newRoleMapping = assignmentResult.RoleMapping; - Log.ZLogDebug($"Role assignment total cost: {assignmentResult.TotalCost:F3}"); - foreach (var unfilledRole in assignmentResult.UnfilledRoles.Where(r => r.IsRequired)) + // Bolt: eliminates string interpolation allocation when debug logging is disabled (~100 allocs/sec) + if (Log.IsEnabled(LogLevel.Debug)) { - Log.ZLogWarning($"Required role left unfilled: {unfilledRole.Role}"); + Log.ZLogDebug($"Role assignment total cost: {assignmentResult.TotalCost:F3}"); + } + + // Bolt: eliminates ~1 enumerator & closure alloc/frame by avoiding LINQ Where() (~100 allocs/sec) + foreach (var unfilledRole in assignmentResult.UnfilledRoles) + { + if (unfilledRole.IsRequired) + { + Log.ZLogWarning($"Required role left unfilled: {unfilledRole.Role}"); + } } Context.Data.Value = Context.Data.Value! with