Skip to content

Commit b7adf37

Browse files
⚡️ Speed up method CommentMapper.visit_AsyncFunctionDef by 11% in PR #687 (granular-async-instrumentation)
The optimized code achieves an 11% speedup through several key micro-optimizations that reduce Python's runtime overhead: **1. Cached Attribute/Dictionary Lookups** The most impactful change is caching frequently accessed attributes and dictionaries as local variables: - `context_stack = self.context_stack` - `results = self.results` - `original_runtimes = self.original_runtimes` - `optimized_runtimes = self.optimized_runtimes` - `get_comment = self.get_comment` This eliminates repeated `self.` attribute lookups in the tight loops, which the profiler shows are called thousands of times (2,825+ iterations). **2. Pre-cached Loop Bodies** Caching `node_body = node.body` and `ln_body = line_node.body` before loops reduces attribute access overhead. The profiler shows these are accessed in nested loops with high hit counts. **3. Optimized String Operations** Using f-strings (`f"{test_qualified_name}#{self.abs_path}"`, `f"{i}_{j}"`) instead of string concatenation with `+` operators reduces temporary object creation and string manipulation overhead. **4. Refined getattr Usage** Changed from `getattr(compound_line_node, "body", [])` to `getattr(compound_line_node, 'body', None)` with a conditional check, avoiding allocation of empty lists when no body exists. **Performance Impact by Test Type:** - **Large-scale tests** show the biggest gains (14-117% faster) due to the cumulative effect of micro-optimizations in loops - **Compound statement tests** benefit significantly (16-45% faster) from reduced attribute lookups in nested processing - **Simple cases** show modest improvements (1-6% faster) as overhead reduction is less pronounced - **Edge cases** with no matching runtimes benefit from faster loop traversal (3-12% faster) The optimizations are most effective for functions with many statements or nested compound structures, where the tight loops amplify the benefit of reduced Python interpreter overhead.
1 parent 3ca6fad commit b7adf37

File tree

1 file changed

+33
-18
lines changed

1 file changed

+33
-18
lines changed

codeflash/code_utils/edit_generated_tests.py

Lines changed: 33 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -60,32 +60,47 @@ def visit_AsyncFunctionDef(self, node: ast.AsyncFunctionDef) -> ast.AsyncFunctio
6060
return node
6161

6262
def _process_function_def_common(self, node: ast.FunctionDef | ast.AsyncFunctionDef) -> None:
63-
self.context_stack.append(node.name)
64-
i = len(node.body) - 1
65-
test_qualified_name = ".".join(self.context_stack)
66-
key = test_qualified_name + "#" + str(self.abs_path)
63+
context_stack = self.context_stack
64+
context_stack.append(node.name)
65+
test_qualified_name = ".".join(context_stack)
66+
key_base = f"{test_qualified_name}#{self.abs_path}"
67+
results = self.results
68+
original_runtimes = self.original_runtimes
69+
optimized_runtimes = self.optimized_runtimes
70+
get_comment = self.get_comment
71+
72+
# Pre-fetch these for loop, reduces attribute+dict lookup cost
73+
node_body = node.body
74+
i = len(node_body) - 1
6775
while i >= 0:
68-
line_node = node.body[i]
76+
line_node = node_body[i]
6977
if isinstance(line_node, (ast.With, ast.For, ast.While, ast.If)):
70-
j = len(line_node.body) - 1
78+
ln_body = line_node.body
79+
j = len(ln_body) - 1
7180
while j >= 0:
72-
compound_line_node: ast.stmt = line_node.body[j]
81+
compound_line_node: ast.stmt = ln_body[j]
82+
# Collect nodes to check
7383
nodes_to_check = [compound_line_node]
74-
nodes_to_check.extend(getattr(compound_line_node, "body", []))
75-
for internal_node in nodes_to_check:
76-
if isinstance(internal_node, (ast.stmt, ast.Assign)):
77-
inv_id = str(i) + "_" + str(j)
78-
match_key = key + "#" + inv_id
79-
if match_key in self.original_runtimes and match_key in self.optimized_runtimes:
80-
self.results[internal_node.lineno] = self.get_comment(match_key)
84+
extend_body = getattr(compound_line_node, "body", None)
85+
if extend_body:
86+
nodes_to_check.extend(extend_body)
87+
inv_id = f"{i}_{j}"
88+
match_key = f"{key_base}#{inv_id}"
89+
if match_key in original_runtimes and match_key in optimized_runtimes:
90+
# Slightly faster to avoid type checks in loop if possible
91+
for internal_node in nodes_to_check:
92+
# is ast.Assign a subclass of ast.stmt? If yes, only need ast.stmt (Assign inherits stmt).
93+
# But original code checks for both, so preserve as-is.
94+
if isinstance(internal_node, (ast.stmt, ast.Assign)):
95+
results[internal_node.lineno] = get_comment(match_key)
8196
j -= 1
8297
else:
8398
inv_id = str(i)
84-
match_key = key + "#" + inv_id
85-
if match_key in self.original_runtimes and match_key in self.optimized_runtimes:
86-
self.results[line_node.lineno] = self.get_comment(match_key)
99+
match_key = f"{key_base}#{inv_id}"
100+
if match_key in original_runtimes and match_key in optimized_runtimes:
101+
results[line_node.lineno] = get_comment(match_key)
87102
i -= 1
88-
self.context_stack.pop()
103+
context_stack.pop()
89104

90105

91106
def get_fn_call_linenos(

0 commit comments

Comments
 (0)