Skip to content

Commit a4a0283

Browse files
committed
run line profiler after selecting best candidate
1 parent a966e25 commit a4a0283

File tree

2 files changed

+75
-7
lines changed

2 files changed

+75
-7
lines changed

codeflash/models/models.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ class BestOptimization(BaseModel):
8181
winning_behavioral_test_results: TestResults
8282
winning_benchmarking_test_results: TestResults
8383
winning_replay_benchmarking_test_results: Optional[TestResults] = None
84-
84+
line_profile_results: Optional[dict] = None
8585

8686
@dataclass(frozen=True)
8787
class BenchmarkKey:

codeflash/optimization/function_optimizer.py

Lines changed: 74 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
TestResults,
7474
TestType,
7575
)
76+
from codeflash.optimization.line_profiler_formatter import format_line_profiler_results
7677
from codeflash.result.create_pr import check_create_pr, existing_tests_source_for
7778
from codeflash.result.critic import coverage_critic, performance_gain, quantity_of_tests_critic, speedup_critic
7879
from codeflash.result.explanation import Explanation
@@ -310,6 +311,12 @@ def optimize_function(self) -> Result[BestOptimization, str]: # noqa: PLR0911
310311
best_optimization.candidate.explanation, title="Best Candidate Explanation", border_style="blue"
311312
)
312313
)
314+
with progress_bar("Running line profiling for optimized code", transient=True):
315+
line_profile_results = self.run_line_profiling_for_best_optimization(
316+
best_optimization=best_optimization,
317+
code_context=code_context,
318+
original_helper_code=original_helper_code,
319+
)
313320
processed_benchmark_info = None
314321
if self.args.benchmark:
315322
processed_benchmark_info = process_benchmark_data(
@@ -780,10 +787,12 @@ def instrument_existing_tests(self, function_to_all_tests: dict[str, set[Functio
780787
continue
781788
# TODO: this naming logic should be moved to a function and made more standard
782789
new_behavioral_test_path = Path(
783-
f"{os.path.splitext(test_file)[0]}__perfinstrumented{os.path.splitext(test_file)[1]}" # noqa: PTH122
790+
f"{os.path.splitext(test_file)[0]}__perfinstrumented{os.path.splitext(test_file)[1]}"
791+
# noqa: PTH122
784792
)
785793
new_perf_test_path = Path(
786-
f"{os.path.splitext(test_file)[0]}__perfonlyinstrumented{os.path.splitext(test_file)[1]}" # noqa: PTH122
794+
f"{os.path.splitext(test_file)[0]}__perfonlyinstrumented{os.path.splitext(test_file)[1]}"
795+
# noqa: PTH122
787796
)
788797
if injected_behavior_test is not None:
789798
with new_behavioral_test_path.open("w", encoding="utf8") as _f:
@@ -819,6 +828,64 @@ def instrument_existing_tests(self, function_to_all_tests: dict[str, set[Functio
819828
)
820829
return unique_instrumented_test_files
821830

831+
#TODO: DRY the code
832+
833+
def run_line_profiling_for_best_optimization(
834+
self,
835+
best_optimization: BestOptimization,
836+
code_context: CodeOptimizationContext,
837+
original_helper_code: dict[Path, str],
838+
) -> dict:
839+
"""Run line profiling specifically for the best optimization candidate."""
840+
if self.args.test_framework != "pytest":
841+
logger.info("Line profiling is only supported for pytest")
842+
return {"timings": {}, "unit": 0, "str_out": ""}
843+
844+
logger.info("Running line profiling for the best optimization...")
845+
846+
# Save current code state
847+
current_fto_code = self.function_to_optimize.file_path.read_text("utf-8")
848+
current_helper_code = {}
849+
for module_abspath in original_helper_code:
850+
current_helper_code[module_abspath] = Path(module_abspath).read_text("utf-8")
851+
852+
try:
853+
# Replace with optimized code
854+
self.replace_function_and_helpers_with_optimized_code(
855+
code_context=code_context,
856+
optimized_code=best_optimization.candidate.source_code,
857+
original_helper_code=original_helper_code,
858+
)
859+
860+
# Add line profiler decorators
861+
line_profiler_output_file = add_decorator_imports(self.function_to_optimize, code_context)
862+
863+
test_env = os.environ.copy()
864+
test_env["CODEFLASH_LOOP_INDEX"] = "0"
865+
test_env["CODEFLASH_TEST_ITERATION"] = "best"
866+
test_env["CODEFLASH_TRACER_DISABLE"] = "1"
867+
if "PYTHONPATH" not in test_env:
868+
test_env["PYTHONPATH"] = str(self.project_root)
869+
else:
870+
test_env["PYTHONPATH"] += os.pathsep + str(self.project_root)
871+
872+
line_profile_results, _ = self.run_and_parse_tests(
873+
testing_type=TestingMode.LINE_PROFILE,
874+
test_env=test_env,
875+
test_files=self.test_files,
876+
optimization_iteration=0,
877+
testing_time=TOTAL_LOOPING_TIME,
878+
enable_coverage=False,
879+
code_context=code_context,
880+
line_profiler_output_file=line_profiler_output_file,
881+
)
882+
883+
return line_profile_results
884+
885+
finally:
886+
# Restore original code
887+
self.write_code_and_helpers(current_fto_code, current_helper_code, self.function_to_optimize.file_path)
888+
822889
def generate_tests_and_optimizations(
823890
self,
824891
testgen_context_code: str,
@@ -981,6 +1048,7 @@ def establish_original_code_baseline(
9811048
code_context=code_context,
9821049
line_profiler_output_file=line_profiler_output_file,
9831050
)
1051+
9841052
finally:
9851053
# Remove codeflash capture
9861054
self.write_code_and_helpers(
@@ -1325,10 +1393,10 @@ def cleanup_generated_files(self) -> None:
13251393
[
13261394
test_file.instrumented_behavior_file_path
13271395
for test_type in [
1328-
TestType.GENERATED_REGRESSION,
1329-
TestType.EXISTING_UNIT_TEST,
1330-
TestType.CONCOLIC_COVERAGE_TEST,
1331-
]
1396+
TestType.GENERATED_REGRESSION,
1397+
TestType.EXISTING_UNIT_TEST,
1398+
TestType.CONCOLIC_COVERAGE_TEST,
1399+
]
13321400
for test_file in self.test_files.get_by_type(test_type).test_files
13331401
]
13341402
+ [

0 commit comments

Comments
 (0)