diff --git a/.jules/bolt.md b/.jules/bolt.md index 4abe6cd4..b18e3b6b 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -4,3 +4,6 @@ ## 2025-11-05 - [Replace HashMap Allocation with Flat Vec for Block Lookup] **Learning:** In the `flatten_blocks` function within `orch8-engine/src/evaluator.rs`, allocating a `HashMap<&BlockId, &BlockDefinition>` to map execution blocks introduces significant hashing and memory allocation overhead on the evaluation hot path, which is called every tick. Since the tree size is typically bounded and small, building a `Vec` and sorting it by `BlockId` allows for O(log N) lookups via `.binary_search_by_key()`, completely avoiding `HashMap` overhead. **Action:** In execution hot paths where a lookup map is created from a slice on every iteration, always prefer a flat `Vec` initialized with `Vec::with_capacity()`, sorted by key, and queried via `.binary_search_by_key()` over a `HashMap` to eliminate hashing and heap allocation costs. +## 2025-11-05 - [Replace Iterator Collect with Manual Loop and Preallocated Vec] +**Learning:** In execution hot paths like `children_of` in `orch8-engine/src/evaluator.rs`, using an iterator adapter chain like `.filter(...).collect()` on a slice doesn't know the final size in advance, causing it to start with 0 capacity and repeatedly reallocate memory as elements are pushed. +**Action:** Replace `.filter(...).collect()` chains with a manual `for` loop that pushes elements to a `Vec` instantiated with `Vec::with_capacity(N)` when a reasonably small bound is known, avoiding initial reallocation overheads. diff --git a/orch8-engine/src/evaluator.rs b/orch8-engine/src/evaluator.rs index f90df856..e0a33a2a 100644 --- a/orch8-engine/src/evaluator.rs +++ b/orch8-engine/src/evaluator.rs @@ -1250,12 +1250,18 @@ pub fn children_of( parent_id: ExecutionNodeId, branch_index: Option, ) -> Vec<&ExecutionNode> { - tree.iter() - .filter(|n| { - n.parent_id == Some(parent_id) - && (branch_index.is_none() || n.branch_index == branch_index) - }) - .collect() + // ⚡ Bolt: Using a manual loop with a preallocated `Vec` avoids the multiple + // reallocation overheads of `.filter(...).collect()` on an unknown-sized + // iterator in execution hot paths. + let mut children = Vec::with_capacity(8); + for n in tree { + if n.parent_id == Some(parent_id) + && (branch_index.is_none() || n.branch_index == branch_index) + { + children.push(n); + } + } + children } /// Check if all nodes in a set are in a terminal state.