From 7f0c8b9a2401dc3d163b38afc05bced037c2f568 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 9 May 2026 21:36:00 +0000 Subject: [PATCH] refactor(search-engine): eliminate O(N) array allocation in searchRemainingItems Replaced the `searchedIndices` Uint8Array tracker allocation in `searchRemainingItems` with an O(1) condition check using `this.itemTypeIds` and `ID_TO_SCOPE` to determine if an item belongs to the priority scopes. This removes the memory allocation, O(N) zero-fill overhead, and GC pressure during the final fallback search phase. Co-authored-by: AhmmedSamier <17784876+AhmmedSamier@users.noreply.github.com> --- .jules/bolt.md | 5 ++++ language-server/benchmarks/results_burst.json | 29 +++++++++++++++++++ language-server/src/core/search-engine.ts | 17 +++-------- 3 files changed, 38 insertions(+), 13 deletions(-) create mode 100644 language-server/benchmarks/results_burst.json diff --git a/.jules/bolt.md b/.jules/bolt.md index cc159b26..fa4fc51c 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -40,3 +40,8 @@ **Learning:** In hot paths that traverse an Abstract Syntax Tree (AST), using dynamic string manipulations like `!parent.type.toLowerCase().includes('class_declaration')` inside a `while` loop creates redundant memory allocations (garbage collection overhead) and slows down the loop significantly. The string `.toLowerCase()` allocation happens on every single node iteration. **Action:** Replace dynamic string manipulations with a static, pre-allocated `Set` of exact node names (e.g., `'class_declaration'`, `'class_definition'`, `'class'`) and use `.has(parent.type)` to achieve O(1) lookups and eliminate string allocation entirely in the loop. + +## 2026-05-06 - [Eliminate Tracking Arrays in O(N) Fallbacks] + +**Learning:** When performing a final multi-pass search or filtering fallback that iterates over the entire dataset (e.g., `searchRemainingItems`), allocating an O(N) tracker array (like `new Uint8Array(items.length)`) and running pre-population loops to mark previously visited items introduces unnecessary memory allocation, garbage collection pressure, and O(N) initialization overhead. +**Action:** Instead of dynamically tracking what has already been processed, evaluate the item's inherent metadata or pre-computed lookup maps (like `ID_TO_SCOPE` combined with `this.itemTypeIds`) to determine if it belongs to an already-processed category (e.g., `priorityScopes.includes(scope)`). This provides an O(1) condition check and completely eliminates the need for visited tracking arrays in the final sweep. diff --git a/language-server/benchmarks/results_burst.json b/language-server/benchmarks/results_burst.json new file mode 100644 index 00000000..1562a224 --- /dev/null +++ b/language-server/benchmarks/results_burst.json @@ -0,0 +1,29 @@ +[ + { + "name": "Burst Search \"App\" (Matches found)", + "avgMs": 0.021544649999999593, + "totalMs": 2.154464999999959, + "minMs": 0.005148999999960324, + "maxMs": 0.18533700000000408, + "p95Ms": 0.07276600000000144, + "stdDevMs": 0.03259625219587125 + }, + { + "name": "Burst Search \"Zzz\" (No match)", + "avgMs": 18.35233173, + "totalMs": 1835.233173, + "minMs": 13.984671000000162, + "maxMs": 33.71546699999999, + "p95Ms": 26.035532999999987, + "stdDevMs": 3.8124350347516467 + }, + { + "name": "Burst Search \"S\" (Many matches)", + "avgMs": 0.5702139400000032, + "totalMs": 57.02139400000033, + "minMs": 0.4487290000001849, + "maxMs": 1.173258999999689, + "p95Ms": 0.6901020000000244, + "stdDevMs": 0.10386148486978344 + } +] \ No newline at end of file diff --git a/language-server/src/core/search-engine.ts b/language-server/src/core/search-engine.ts index 5a5e000a..2c6d6bbf 100644 --- a/language-server/src/core/search-engine.ts +++ b/language-server/src/core/search-engine.ts @@ -2340,21 +2340,12 @@ export class SearchEngine implements ISearchProvider { results: SearchResult[], token?: CancellationToken, ): void { - // ⚡ Bolt: Fast Unique Tracking Optimization - // Replace Set with a pre-allocated Uint8Array - const searchedIndices = new Uint8Array(this.items.length); - for (let j = 0; j < priorityScopes.length; j++) { - const indices = this.scopedIndices.get(priorityScopes[j]); - if (indices) { - for (let k = 0; k < indices.length; k++) { - searchedIndices[indices[k]] = 1; - } - } - } - + // ⚡ Bolt: Eliminate Tracking Array Allocation + // Check the item's inherent scope instead of allocating an O(N) visited array for (let i = 0; i < this.items.length; i++) { if (results.length >= maxResults || token?.isCancellationRequested) break; - if (searchedIndices[i] === 0) { + const scope = ID_TO_SCOPE[this.itemTypeIds[i]]; + if (!priorityScopes.includes(scope)) { processItem(i); } }