Skip to content

Commit b69f4d5

Browse files
Optimize JavaLineProfiler.instrument_function
Runtime improvement: the optimized version runs ~25% faster (2.06 ms -> 1.64 ms), with larger wins on functions with many instrumentable lines. What changed - Precompute file_path.as_posix() once per function: file_posix = file_path.as_posix() and reuse it for content_key and the profiled f-string instead of calling file_path.as_posix() repeatedly inside the loop. - Combined multiple startswith checks into a single startswith(("//", "/*", "*")) call to replace three separate str.startswith() calls. Why this speeds things up - Attribute lookups and small method calls are relatively expensive in Python. In the original code file_path.as_posix() was called twice for every instrumented line (once for content_key and once inside the profiled f-string). Moving that result into a local variable removes those repeated method calls and attribute lookups and replaces them with a fast local variable load. - Multiple str.startswith() calls were replaced by one startswith(tuple) call, cutting the number of Python-level function calls and condition checks for candidate comment lines. - These savings multiply with the number of lines inside a function. The loop is the hot path: each instrumented statement previously did several extra method calls and string operations; removing them reduces per-line overhead and thus the total runtime. Observed evidence - Line-profiling shows heavy time spent on the two expressions that used file_path.as_posix() repeatedly; those costs drop in the optimized profile. - The annotated tests show the biggest improvements on the large-scale test (many lines), which matches the expectation that per-line micro-optimizations are most beneficial when the loop has many iterations. Behavioral impact and trade-offs - No functional change: the instrumented output and semantics are unchanged. - A tiny upfront cost (computing file_posix once) is paid even if no lines are instrumented, but that's negligible and worth the per-line savings. - The exception path (logger.warning) shows a larger percent of the shorter total time in the optimized profile — this is not a regression in practice, just a profiling artifact because the overall runtime decreased; exception handling remains unchanged. When this matters most - Hot paths that instrument long functions or many functions (the large-scale test) gain the most. - Small functions still benefit (the overall runtime measured improved), but the relative improvement is smaller because fixed costs (parse, etc.) dominate. Summary Precomputing file_posix and reducing redundant startswith/attribute calls cut down repeated Python-level work inside the main loop. That directly lowers per-line overhead and yields the measured ~25% runtime improvement, especially on workloads with many instrumentable lines.
1 parent 96e231e commit b69f4d5

1 file changed

Lines changed: 5 additions & 7 deletions

File tree

codeflash/languages/java/line_profiler.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,8 @@ def instrument_function(self, func: FunctionInfo, lines: list[str], file_path: P
353353
# Add profiling to each executable line
354354
function_entry_added = False
355355

356+
file_posix = file_path.as_posix()
357+
356358
for local_idx, line in enumerate(func_lines):
357359
local_line_num = local_idx + 1 # 1-indexed within function
358360
global_line_num = func.starting_line + local_idx # Global line number
@@ -377,23 +379,19 @@ def instrument_function(self, func: FunctionInfo, lines: list[str], file_path: P
377379
if (
378380
local_line_num in executable_lines
379381
and stripped
380-
and not stripped.startswith("//")
381-
and not stripped.startswith("/*")
382-
and not stripped.startswith("*")
382+
and not stripped.startswith(("//", "/*", "*"))
383383
and stripped not in ("}", "};")
384384
):
385385
# Get indentation
386386
indent = len(line) - len(line.lstrip())
387387
indent_str = " " * indent
388388

389389
# Store line content for profiler output
390-
content_key = f"{file_path.as_posix()}:{global_line_num}"
390+
content_key = f"{file_posix}:{global_line_num}"
391391
self.line_contents[content_key] = stripped
392392

393393
# Add hit() call before the line
394-
profiled_line = (
395-
f'{indent_str}{self.profiler_class}.hit("{file_path.as_posix()}", {global_line_num});\n{line}'
396-
)
394+
profiled_line = f'{indent_str}{self.profiler_class}.hit("{file_posix}", {global_line_num});\n{line}'
397395
instrumented_lines.append(profiled_line)
398396
else:
399397
instrumented_lines.append(line)

0 commit comments

Comments
 (0)