From 419f38049d1866533a14b544753ac0e116bb9ddd Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 2 Oct 2026 15:09:58 +0000 Subject: [PATCH] perf(backtest): avoid chained array allocations in ticker dedupe Replaces chained .map(), object spreads, Set creation, Array.from(), and .slice() operations with a bounded for-loop when deduplicating and bounding tickers during the backtest turn. This avoids creating multiple intermediate arrays and allows early exit, significantly reducing GC overhead in the hot loop. Co-authored-by: toreleon <42534763+toreleon@users.noreply.github.com> --- .jules/bolt.md | 4 ++++ src/agent/backtestRunner.ts | 26 ++++++++++++++++++++++---- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/.jules/bolt.md b/.jules/bolt.md index 289fae6..d2c781d 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -23,3 +23,7 @@ ## 2026-09-19 - Avoid chained array methods before Set initialization **Learning:** Chaining `.map().filter()` inside a `new Set()` constructor creates multiple intermediate arrays, causing unnecessary memory allocation and garbage collection overhead in hot paths (like ticker normalization during discovery). **Action:** Replace chained array methods with a direct indexed `for` loop that transforms, validates, and `.add()`s directly to a freshly initialized `Set`. + +## 2026-10-02 - Avoid chained array operations during duplicate filtering +**Learning:** When generating a bounded, deduplicated array in hot paths (like ticker generation in `src/agent/backtestRunner.ts`), chaining `.map()`, spreads, `new Set()`, `Array.from()`, and `.slice()` causes excessive array allocations and garbage collection overhead, converting an O(K) process to O(N). +**Action:** Replaced the chained methods with an imperative, bounded `for` loop and early loop exit. This significantly reduced allocations in the hot path without sacrificing correctness. diff --git a/src/agent/backtestRunner.ts b/src/agent/backtestRunner.ts index e53b03c..22d8b0e 100644 --- a/src/agent/backtestRunner.ts +++ b/src/agent/backtestRunner.ts @@ -347,10 +347,28 @@ export async function runBacktestSession( universe: BACKTEST_DISCOVERY_UNIVERSE, limit: maxCandidates, }); - const held = (await broker.snapshot()).positions.map((p) => p.ticker); - const tickers = Array.from( - new Set([...held, ...discovery.candidates.map((c) => c.ticker)]), - ).slice(0, maxCandidates); + // ⚡ Bolt: Replaced chained .map(), spread, Set, Array.from, and .slice() + // with a bounded for-loop to reduce O(N) array allocations and GC overhead + // in the hot backtesting loop. Exits early when maxCandidates is reached. + const snapPositions = (await broker.snapshot()).positions; + const tickers: string[] = []; + const seen = new Set(); + + for (let i = 0; i < snapPositions.length && tickers.length < maxCandidates; i++) { + const t = snapPositions[i]!.ticker; + if (!seen.has(t)) { + seen.add(t); + tickers.push(t); + } + } + + for (let i = 0; i < discovery.candidates.length && tickers.length < maxCandidates; i++) { + const t = discovery.candidates[i]!.ticker; + if (!seen.has(t)) { + seen.add(t); + tickers.push(t); + } + } const decisions: FinalDecision[] = []; for (const ticker of tickers) {