diff --git a/.jules/bolt.md b/.jules/bolt.md index 51399205..86371a76 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -63,3 +63,6 @@ ## 2023-10-24 - Pre-allocate and clone IndexMap in database row iteration **Learning:** When fetching rows from a database where each row's values are stored in an `IndexMap` keyed by the column name, constructing a new `IndexMap` for every row involves heavy performance penalties due to repeated string cloning (for the keys) and hashing operations. Creating a single "prototype" map with null values and cloning it for each row provides a fast memory copy that completely bypasses the hashing phase. However, `IndexMap` auto-deduplicates keys during insertion, so the mapping from database column index to `IndexMap` index must be carefully pre-computed to safely update values by index using `get_index_mut`. **Action:** In loops over data with a fixed schema (like database rows or CSVs) where the target structure is an `IndexMap`, always pre-allocate a prototype map and clone it per iteration rather than constructing a new map and hashing keys for every single element. +## 2023-11-09 - Dominator Analysis Optimization +**Learning:** In fixed-point iterative algorithms like the dominator tree analysis, using `Option>` and continuously allocating or cloning new sets (`new_dom = Some(pred_doms.clone())`, `all_blocks.clone()`) inside the inner loop is extremely expensive and produces O(N) intermediate allocations per block per iteration. +**Action:** Always pre-allocate a single mutable `HashSet` outside the loop, use `.clear()` each iteration, and compute in-place intersections using `.extend()` and `.retain()`. Also, use `.clone_from()` for updates to reuse existing capacity instead of reassignment. diff --git a/compiler/optimizer/src/analysis/dominators.rs b/compiler/optimizer/src/analysis/dominators.rs index 8e5c3f84..2564a9e1 100644 --- a/compiler/optimizer/src/analysis/dominators.rs +++ b/compiler/optimizer/src/analysis/dominators.rs @@ -34,6 +34,8 @@ impl DominatorAnalysis { // Iterative algorithm to compute dominators let mut changed = true; + let mut new_dom = HashSet::new(); + while changed { changed = false; for block in &func.blocks { @@ -41,24 +43,29 @@ impl DominatorAnalysis { continue; } - let mut new_dom: Option> = None; + new_dom.clear(); + let mut first = true; + for &pred in &block.predecessors { if let Some(pred_doms) = dominators.get(&pred) { - if let Some(mut current_dom) = new_dom.take() { - current_dom.retain(|x| pred_doms.contains(x)); - new_dom = Some(current_dom); + if first { + new_dom.extend(pred_doms.iter().copied()); + first = false; } else { - new_dom = Some(pred_doms.clone()); + new_dom.retain(|x| pred_doms.contains(x)); } } } - let mut new_dom = new_dom.unwrap_or_else(|| all_blocks.clone()); + if first { + new_dom.extend(all_blocks.iter().copied()); + } new_dom.insert(block.id); if let Some(old_doms) = dominators.get_mut(&block.id) { - if *old_doms != new_dom { - *old_doms = new_dom; + if old_doms.len() != new_dom.len() { + // OPTIMIZATION: Reuse existing HashSet capacity to avoid allocations in hot loop. + old_doms.clone_from(&new_dom); changed = true; } }