|
73 | 73 | TestResults, |
74 | 74 | TestType, |
75 | 75 | ) |
| 76 | +from codeflash.optimization.line_profiler_formatter import format_line_profiler_results |
76 | 77 | from codeflash.result.create_pr import check_create_pr, existing_tests_source_for |
77 | 78 | from codeflash.result.critic import coverage_critic, performance_gain, quantity_of_tests_critic, speedup_critic |
78 | 79 | from codeflash.result.explanation import Explanation |
@@ -310,6 +311,12 @@ def optimize_function(self) -> Result[BestOptimization, str]: # noqa: PLR0911 |
310 | 311 | best_optimization.candidate.explanation, title="Best Candidate Explanation", border_style="blue" |
311 | 312 | ) |
312 | 313 | ) |
| 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 | + ) |
313 | 320 | processed_benchmark_info = None |
314 | 321 | if self.args.benchmark: |
315 | 322 | processed_benchmark_info = process_benchmark_data( |
@@ -780,10 +787,12 @@ def instrument_existing_tests(self, function_to_all_tests: dict[str, set[Functio |
780 | 787 | continue |
781 | 788 | # TODO: this naming logic should be moved to a function and made more standard |
782 | 789 | 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 |
784 | 792 | ) |
785 | 793 | 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 |
787 | 796 | ) |
788 | 797 | if injected_behavior_test is not None: |
789 | 798 | 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 |
819 | 828 | ) |
820 | 829 | return unique_instrumented_test_files |
821 | 830 |
|
| 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 | + |
822 | 889 | def generate_tests_and_optimizations( |
823 | 890 | self, |
824 | 891 | testgen_context_code: str, |
@@ -981,6 +1048,7 @@ def establish_original_code_baseline( |
981 | 1048 | code_context=code_context, |
982 | 1049 | line_profiler_output_file=line_profiler_output_file, |
983 | 1050 | ) |
| 1051 | + |
984 | 1052 | finally: |
985 | 1053 | # Remove codeflash capture |
986 | 1054 | self.write_code_and_helpers( |
@@ -1325,10 +1393,10 @@ def cleanup_generated_files(self) -> None: |
1325 | 1393 | [ |
1326 | 1394 | test_file.instrumented_behavior_file_path |
1327 | 1395 | 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 | + ] |
1332 | 1400 | for test_file in self.test_files.get_by_type(test_type).test_files |
1333 | 1401 | ] |
1334 | 1402 | + [ |
|
0 commit comments