⚡ Bolt: [performance improvement] Defer PathBuf allocations during DAG traversal#178
⚡ Bolt: [performance improvement] Defer PathBuf allocations during DAG traversal#178bashandbone wants to merge 1 commit intomainfrom
Conversation
…G traversal - Compute PathBuf once during Tarjan DFS traversal instead of per map lookup - Use borrowed &Path reference for Hash Map lookups inside tight loops - Avoids O(E) redundant heap allocations - Fixes minor clippy lifetime warnings in check_var.rs Co-authored-by: bashandbone <89049923+bashandbone@users.noreply.github.com>
|
👋 Jules, reporting for duty! I'm here to lend a hand with this pull request. When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down. I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job! For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with New to Jules? Learn more at jules.google/docs. For security, I will only act on instructions from the user who triggered this task. |
Reviewer's guide (collapsed on small PRs)Reviewer's GuideRefactors the Tarjan DFS invalidation traversal to avoid repeated PathBuf allocations by computing an owned path once per node and using borrowed Path references for hot-loop lookups, along with a small Clippy-driven lifetime cleanup in the rule engine utilities. Class diagram for Tarjan_dfs invalidation optimizationclassDiagram
class InvalidationDetector {
+graph: DependencyGraph
+tarjan_dfs(v: Path, state: TarjanState, sccs: Vec_PathBuf_)
}
class TarjanState {
+indices: HashMap_PathBuf_usize_
+lowlinks: HashMap_PathBuf_usize_
+stack: Vec_PathBuf_
+on_stack: HashSet_PathBuf_
+index_counter: usize
}
class DependencyGraph {
+get_dependencies(path: Path): Vec_Path_
}
InvalidationDetector --> DependencyGraph : uses
InvalidationDetector --> TarjanState : mutates
TarjanState o-- PathBuf : stores
TarjanState o-- usize : stores
%% Focus on optimized ownership and borrowing inside tarjan_dfs
class TarjanDfsFlow {
+v: Path
+v_buf: PathBuf
+initialize_node()
+update_lowlinks_with_borrowed_v()
+compute_root_and_emit_scc()
}
TarjanDfsFlow --> TarjanState : writes_indices_lowlinks_stack_on_stack
TarjanDfsFlow --> DependencyGraph : reads_dependencies
TarjanDfsFlow --> Path : borrows_v
TarjanDfsFlow --> PathBuf : owns_v_buf
Flow diagram for optimized Tarjan_dfs PathBuf allocationflowchart TD
A[start tarjan_dfs with v Path] --> B[compute v_buf PathBuf once]
B --> C[insert v_buf clone into state.indices]
C --> D[insert v_buf clone into state.lowlinks]
D --> E[push v_buf clone onto state.stack]
E --> F[insert v_buf into state.on_stack]
F --> G[for each dependency dep from graph.get_dependencies v]
G --> H{dep in state.indices?}
H -->|no| I[recurse tarjan_dfs dep]
I --> J[update v_lowlink using state.lowlinks.get_mut v]
H -->|yes and dep on_stack| K[update v_lowlink using state.lowlinks.get_mut v]
J --> L[continue loop]
K --> L
L --> G
G -->|done| M[read v_index from state.indices.get v]
M --> N[read v_lowlink from state.lowlinks.get v]
N --> O{v_lowlink == v_index?}
O -->|yes| P[pop from state.stack until v reached]
P --> Q[emit SCC into sccs]
O -->|no| R[end tarjan_dfs frame]
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've left some high level feedback:
- You still pay for multiple
PathBufclones when initializingtarjan_dfs; consider storing&Pathinstack/on_stackand only materializingPathBufs when emitting SCCs if lifetimes allow, to eliminate almost all per-node allocations. - The inline
// ⚡ Bolt Optimizationcomments are quite verbose and largely restate the diff; trimming them down to a short, neutral explanation (or relying on the PR description) would make this hot-path code easier to scan.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- You still pay for multiple `PathBuf` clones when initializing `tarjan_dfs`; consider storing `&Path` in `stack`/`on_stack` and only materializing `PathBuf`s when emitting SCCs if lifetimes allow, to eliminate almost all per-node allocations.
- The inline `// ⚡ Bolt Optimization` comments are quite verbose and largely restate the diff; trimming them down to a short, neutral explanation (or relying on the PR description) would make this hot-path code easier to scan.Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
There was a problem hiding this comment.
Pull request overview
Refactors Tarjan DFS in incremental invalidation to avoid repeated PathBuf allocations during dependency graph traversal, and cleans up unnecessary explicit lifetimes that triggered Clippy warnings.
Changes:
- Cache
v.to_path_buf()once during Tarjan node initialization and switch inner-loop map lookups to use borrowed&Pathkeys. - Remove explicit lifetimes from
check_var_in_constraints/check_var_in_transformsignatures.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| crates/rule-engine/src/check_var.rs | Removes redundant explicit lifetimes to satisfy Clippy / simplify signatures. |
| crates/flow/src/incremental/invalidation.rs | Reduces allocation churn in Tarjan SCC traversal by avoiding per-edge PathBuf creation for map lookups. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| let index = state.index_counter; | ||
| state.indices.insert(v.to_path_buf(), index); | ||
| state.lowlinks.insert(v.to_path_buf(), index); | ||
| // ⚡ Bolt Optimization: Compute owned PathBuf once for insertion to avoid redundant O(E) allocations |
There was a problem hiding this comment.
The comment claims this avoids redundant "O(E) allocations", but this initialization block runs once per visited vertex (O(V)), and the clone() calls still allocate per owned PathBuf stored in each collection. Consider rewording to describe the actual win here (avoiding per-edge temporary PathBuf allocations during lookups), or just remove the complexity discussion from this line-level comment.
| // ⚡ Bolt Optimization: Compute owned PathBuf once for insertion to avoid redundant O(E) allocations | |
| // ⚡ Bolt Optimization: Materialize one owned `PathBuf` up front for the | |
| // inserts/push below, while later lookups can continue to use borrowed `&Path`s. |
💡 What: Refactored
tarjan_dfsincrates/flow/src/incremental/invalidation.rsto computev.to_path_buf()only once when initializing the node, and subsequently used the borrowedv(&Path) reference for all inner-loop lookups in HashMaps (state.lowlinks.get_mut(v)andstate.indices.get(v)).🎯 Why: In the hot loop traversing all edges of the dependency graph (O(E)),
v.to_path_buf()was previously called repeatedly. This meant that for every edge, a newPathBufwas being heap-allocated just to do a map lookup, causing high memory churn and severely degrading traversal performance on large graphs.📊 Impact: This drastically reduces string allocation churn during the dependency graph traversal, turning O(E) heap allocations into O(V) or effectively zero allocations inside the traversal loop. This directly speeds up the incremental invalidation cycle.
🔬 Measurement: Run the performance tests using
cargo test -p thread-flow --test invalidation_testsand observe improved or consistently fast completion times for tests liketest_large_graph_performanceandtest_wide_fanout_performance.(Also included a minor cleanup to remove unnecessary explicit lifetimes in
crates/rule-engine/src/check_var.rsthat were throwing Clippy warnings).PR created automatically by Jules for task 15373065001613403940 started by @bashandbone
Summary by Sourcery
Optimize Tarjan DFS invalidation traversal to reduce path allocations and perform minor cleanup in rule-engine variable checking utilities.
Enhancements: