diff --git a/.jules/bolt.md b/.jules/bolt.md index 4abe6cd4..70434744 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -4,3 +4,7 @@ ## 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-06 - [Optimize children_of using Vec::with_capacity] +**Learning:** In `orch8-engine/src/evaluator.rs`, the `children_of` function is called heavily on the hot path for all execution node evaluations. It previously chained `.filter(...).collect()` on the execution tree slice. Because `.filter()` creates an iterator of unknown size, `.collect()` causes the resulting `Vec` to allocate and potentially reallocate multiple times as elements are found. +**Action:** Replace `.filter(...).collect()` chains in hot paths with a manual `for` loop pushing to a `Vec` instantiated with `Vec::with_capacity(N)` when a reasonable small bound (like 8 for most child node scenarios) is known. This significantly eliminates redundant iterator and allocator overhead. diff --git a/orch8-engine/src/evaluator.rs b/orch8-engine/src/evaluator.rs index f90df856..4231ecc2 100644 --- a/orch8-engine/src/evaluator.rs +++ b/orch8-engine/src/evaluator.rs @@ -1250,12 +1250,19 @@ 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: Replace `.filter().collect()` with a manual loop and `Vec::with_capacity` + // to avoid multiple reallocation overheads when collecting an unknown-sized iterator. + // Most nodes have a small number of direct children, so preallocating a small capacity + // is highly efficient. + 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.