From 49362041bb3fee1ffb40e65628c46eef9eac42a5 Mon Sep 17 00:00:00 2001 From: "codeflash-ai[bot]" <148906541+codeflash-ai[bot]@users.noreply.github.com> Date: Sun, 15 Mar 2026 02:05:57 +0000 Subject: [PATCH 1/2] Optimize CallGraph.ancestors The optimization replaces the per-iteration `max_depth is not None and depth >= max_depth` check with a single upfront branch that runs two specialized BFS variants: one without depth tracking (storing plain `FunctionNode` in the queue) when `max_depth` is None, and one with depth tracking (storing `tuple[FunctionNode, int]`) when a limit is set. This eliminates tuple packing/unpacking and a conditional check on every loop iteration in the common unlimited-depth case. Line profiler shows the original `for edge in self.callers_of(current)` accounted for 91% of runtime; the optimized code caches `self.reverse` once and uses `reverse_map.get(current, [])` inline, avoiding 8309 redundant dictionary lookups. The trade-off is slightly longer code due to the two-path structure, but runtime improves 37% with negligible regressions in a few edge cases (max_depth=0 is 66% slower, but these are rare micro-benchmarks with sub-microsecond absolute deltas). --- codeflash/models/call_graph.py | 44 +++++++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/codeflash/models/call_graph.py b/codeflash/models/call_graph.py index ee2521c4f..13637d08e 100644 --- a/codeflash/models/call_graph.py +++ b/codeflash/models/call_graph.py @@ -98,15 +98,27 @@ def descendants(self, node: FunctionNode, max_depth: int | None = None) -> set[F def ancestors(self, node: FunctionNode, max_depth: int | None = None) -> set[FunctionNode]: visited: set[FunctionNode] = set() - queue: deque[tuple[FunctionNode, int]] = deque([(node, 0)]) - while queue: - current, depth = queue.popleft() - if max_depth is not None and depth >= max_depth: - continue - for edge in self.callers_of(current): - if edge.caller not in visited: - visited.add(edge.caller) - queue.append((edge.caller, depth + 1)) + reverse_map = self.reverse + + if max_depth is None: + queue: deque[FunctionNode] = deque([node]) + while queue: + current = queue.popleft() + for edge in reverse_map.get(current, []): + if edge.caller not in visited: + visited.add(edge.caller) + queue.append(edge.caller) + else: + queue_with_depth: deque[tuple[FunctionNode, int]] = deque([(node, 0)]) + while queue_with_depth: + current, depth = queue_with_depth.popleft() + if depth >= max_depth: + continue + for edge in reverse_map.get(current, []): + if edge.caller not in visited: + visited.add(edge.caller) + queue_with_depth.append((edge.caller, depth + 1)) + return visited def subgraph(self, nodes: set[FunctionNode]) -> CallGraph: @@ -149,6 +161,20 @@ def topological_order(self) -> list[FunctionNode]: result.reverse() return result + def _build_reverse(self) -> dict[FunctionNode, list[CallEdge]]: + rev: dict[FunctionNode, list[CallEdge]] = {} + for e in self.edges: + rev.setdefault(e.callee, []).append(e) + return rev + + @property + def reverse(self) -> dict[FunctionNode, list[CallEdge]]: + rv = self._reverse + if rv is None: + rv = self._build_reverse() + self._reverse = rv + return rv + def augment_with_trace(graph: CallGraph, trace_db_path: Path) -> CallGraph: import sqlite3 From 3ad701fb5c4a08a28ac73fd66bc3d1bd799f10ed Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Sun, 15 Mar 2026 02:09:15 +0000 Subject: [PATCH 2/2] fix: remove duplicate reverse property and dead _build_reverse method The optimizer added a second 'reverse' property at the bottom of CallGraph that shadowed the existing one (F811), leaving the original as dead code. Remove the duplicate and the unnecessary _build_reverse helper; the existing reverse property via _build_adjacency is sufficient. --- codeflash/models/call_graph.py | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/codeflash/models/call_graph.py b/codeflash/models/call_graph.py index 13637d08e..b0d800dca 100644 --- a/codeflash/models/call_graph.py +++ b/codeflash/models/call_graph.py @@ -99,7 +99,7 @@ def descendants(self, node: FunctionNode, max_depth: int | None = None) -> set[F def ancestors(self, node: FunctionNode, max_depth: int | None = None) -> set[FunctionNode]: visited: set[FunctionNode] = set() reverse_map = self.reverse - + if max_depth is None: queue: deque[FunctionNode] = deque([node]) while queue: @@ -118,7 +118,7 @@ def ancestors(self, node: FunctionNode, max_depth: int | None = None) -> set[Fun if edge.caller not in visited: visited.add(edge.caller) queue_with_depth.append((edge.caller, depth + 1)) - + return visited def subgraph(self, nodes: set[FunctionNode]) -> CallGraph: @@ -161,20 +161,6 @@ def topological_order(self) -> list[FunctionNode]: result.reverse() return result - def _build_reverse(self) -> dict[FunctionNode, list[CallEdge]]: - rev: dict[FunctionNode, list[CallEdge]] = {} - for e in self.edges: - rev.setdefault(e.callee, []).append(e) - return rev - - @property - def reverse(self) -> dict[FunctionNode, list[CallEdge]]: - rv = self._reverse - if rv is None: - rv = self._build_reverse() - self._reverse = rv - return rv - def augment_with_trace(graph: CallGraph, trace_db_path: Path) -> CallGraph: import sqlite3