From 1447c5e98f683b0904cef0524e5228264f342129 Mon Sep 17 00:00:00 2001 From: Lukas Rothenberger Date: Mon, 28 Oct 2024 09:26:35 +0100 Subject: [PATCH] feat(autotuner): add --sanitize argument (to invoke ThreadSanitizer) --- .../EmpiricalAutotuning/ArgumentClasses.py | 1 + .../EmpiricalAutotuning/Autotuner.py | 16 ++++++++------ .../Classes/CodeConfiguration.py | 21 +++++++++++++++++-- .../Classes/ExecutionResult.py | 14 +++++++++++-- .../EmpiricalAutotuning/__main__.py | 8 ++++--- 5 files changed, 47 insertions(+), 13 deletions(-) diff --git a/discopop_library/EmpiricalAutotuning/ArgumentClasses.py b/discopop_library/EmpiricalAutotuning/ArgumentClasses.py index 4b5d69a5d..ae9ba996c 100644 --- a/discopop_library/EmpiricalAutotuning/ArgumentClasses.py +++ b/discopop_library/EmpiricalAutotuning/ArgumentClasses.py @@ -21,6 +21,7 @@ class AutotunerArguments(GeneralArguments): project_path: str dot_dp_path: str skip_cleanup: bool + sanitize: bool def __post_init__(self) -> None: self.__validate() diff --git a/discopop_library/EmpiricalAutotuning/Autotuner.py b/discopop_library/EmpiricalAutotuning/Autotuner.py index a906d120b..70e150bfc 100644 --- a/discopop_library/EmpiricalAutotuning/Autotuner.py +++ b/discopop_library/EmpiricalAutotuning/Autotuner.py @@ -39,13 +39,13 @@ def get_unique_configuration_id() -> int: def run(arguments: AutotunerArguments) -> None: logger.info("Starting discopop autotuner.") - debug_stats: List[Tuple[List[SUGGESTION_ID], float, int, bool, str]] = [] + debug_stats: List[Tuple[List[SUGGESTION_ID], float, int, bool, bool, str]] = [] statistics_graph = StatisticsGraph() statistics_step_num = 0 # get untuned reference result reference_configuration = CodeConfiguration(arguments.project_path, arguments.dot_dp_path) - reference_configuration.execute(timeout=None, is_initial=True) + reference_configuration.execute(arguments, timeout=None, is_initial=True) statistics_graph.set_root( reference_configuration.get_statistics_graph_label(), color=reference_configuration.get_statistics_graph_color(), @@ -58,6 +58,7 @@ def run(arguments: AutotunerArguments) -> None: cast(ExecutionResult, reference_configuration.execution_result).runtime, cast(ExecutionResult, reference_configuration.execution_result).return_code, cast(ExecutionResult, reference_configuration.execution_result).result_valid, + cast(ExecutionResult, reference_configuration.execution_result).thread_sanitizer, reference_configuration.root_path, ) ) @@ -141,7 +142,7 @@ def run(arguments: AutotunerArguments) -> None: visited_configurations.append(current_config) tmp_config = reference_configuration.create_copy(get_unique_configuration_id) tmp_config.apply_suggestions(arguments, current_config) - tmp_config.execute(timeout=timeout_after) + tmp_config.execute(arguments, timeout=timeout_after) statistics_graph.add_child( "step " + str(statistics_step_num) @@ -159,6 +160,7 @@ def run(arguments: AutotunerArguments) -> None: cast(ExecutionResult, tmp_config.execution_result).runtime, cast(ExecutionResult, tmp_config.execution_result).return_code, cast(ExecutionResult, tmp_config.execution_result).result_valid, + cast(ExecutionResult, tmp_config.execution_result).thread_sanitizer, tmp_config.root_path, ) ) @@ -241,7 +243,7 @@ def run(arguments: AutotunerArguments) -> None: # show debug stats stats_str = "Configuration measurements:\n" - stats_str += "[time]\t[applied suggestions]\t[return code]\t[result valid]\t[path]\n" + stats_str += "[time]\t[applied suggestions]\t[return code]\t[result valid]\t[thread sanitizer]\t[path]\n" for stats in sorted(debug_stats, key=lambda x: (x[1]), reverse=True): stats_str += ( str(round(stats[1], 3)) @@ -254,6 +256,8 @@ def run(arguments: AutotunerArguments) -> None: + str(stats[3]) + "\t" + str(stats[4]) + + "\t" + + str(stats[5]) + "\n" ) logger.info(stats_str) @@ -261,12 +265,12 @@ def run(arguments: AutotunerArguments) -> None: # export measurements for pdf creation if False: # export all measurements with open("measurements.csv", "w+") as f: - f.write("ID; time; return_code;\n") + f.write("ID; time; return_code; thread_sanitizer\n") for stats in sorted(debug_stats, key=lambda x: x[1], reverse=True): f.write(str(stats[0]) + "; " + str(round(stats[1], 3)) + "; " + str(stats[2]) + ";" + "\n") else: # export only sequential and best measurement with open("measurements.csv", "w+") as f: - f.write("ID; time; return_code;\n") + f.write("ID; time; return_code; thread_sanitizer\n") # write sequential measurement for stats in sorted(debug_stats, key=lambda x: x[1], reverse=True): if str(stats[0]) == "[]": diff --git a/discopop_library/EmpiricalAutotuning/Classes/CodeConfiguration.py b/discopop_library/EmpiricalAutotuning/Classes/CodeConfiguration.py index be7bdcdc4..0b88ddbb9 100644 --- a/discopop_library/EmpiricalAutotuning/Classes/CodeConfiguration.py +++ b/discopop_library/EmpiricalAutotuning/Classes/CodeConfiguration.py @@ -41,7 +41,7 @@ def __init__(self, root_path: str, config_dot_dp_path: str): def __str__(self) -> str: return self.root_path - def execute(self, timeout: Optional[float], is_initial: bool = False) -> None: + def execute(self, arguments: AutotunerArguments, timeout: Optional[float], is_initial: bool = False) -> None: # create timeout string timeout_string = "" if timeout is None else "timeout " + str(timeout) + " " if is_initial: @@ -77,12 +77,29 @@ def execute(self, timeout: Optional[float], is_initial: bool = False) -> None: if validity_check_result.returncode != 0: result_valid = False + # check for result validity using thread sanitizer + thread_sanitizer_valid = True + if os.path.exists(os.path.join(self.root_path, "DP_SANITIZE.sh")) and arguments.sanitize: + logger.info("Checking thread sanity: " + str(self)) + thread_sanitizer_result = subprocess.run( + "./DP_SANITIZE.sh && ./DP_EXECUTE.sh", + cwd=self.root_path, + executable="/bin/bash", + shell=True, + capture_output=True, + ) + thread_sanitizer_output = str(thread_sanitizer_result.stdout.decode("utf-8")) + logger.getChild("ThreadSanitizerOutput").debug(thread_sanitizer_output) + if "WARNING: ThreadSanitizer: data race" in thread_sanitizer_output: + thread_sanitizer_valid = False + # reporting logger.debug("Execution took " + str(round(required_time, 4)) + " s") logger.debug("Execution return code: " + str(result.returncode)) logger.debug("Execution result valid: " + str(result_valid)) + logger.debug("ThreadSanitizer valid: " + str(thread_sanitizer_valid)) - self.execution_result = ExecutionResult(required_time, result.returncode, result_valid) + self.execution_result = ExecutionResult(required_time, result.returncode, result_valid, thread_sanitizer_valid) def create_copy(self, get_new_configuration_id: Callable[[], int]) -> CodeConfiguration: # create a copy of the project folder diff --git a/discopop_library/EmpiricalAutotuning/Classes/ExecutionResult.py b/discopop_library/EmpiricalAutotuning/Classes/ExecutionResult.py index e614e955a..9fb322431 100644 --- a/discopop_library/EmpiricalAutotuning/Classes/ExecutionResult.py +++ b/discopop_library/EmpiricalAutotuning/Classes/ExecutionResult.py @@ -11,13 +11,23 @@ class ExecutionResult(object): runtime: float return_code: int result_valid: bool + thread_sanitizer: bool - def __init__(self, runtime: float, return_code: int, result_valid: bool): + def __init__(self, runtime: float, return_code: int, result_valid: bool, thread_sanitizer: bool): self.runtime = runtime self.return_code = return_code self.result_valid = result_valid + self.thread_sanitizer = thread_sanitizer def __str__(self) -> str: return ( - "" + "time: " + str(self.runtime) + " code: " + str(self.return_code) + " valid: " + str(self.result_valid) + "" + + "time: " + + str(self.runtime) + + " code: " + + str(self.return_code) + + " valid: " + + str(self.result_valid) + + " TSAN: " + + str(self.thread_sanitizer) ) diff --git a/discopop_library/EmpiricalAutotuning/__main__.py b/discopop_library/EmpiricalAutotuning/__main__.py index 166e85d1d..d7989c854 100644 --- a/discopop_library/EmpiricalAutotuning/__main__.py +++ b/discopop_library/EmpiricalAutotuning/__main__.py @@ -18,7 +18,7 @@ def parse_args() -> AutotunerArguments: parser = ArgumentParser(description="DiscoPoP Autotuner") # fmt: off - + parser.add_argument("--log", type=str, default="WARNING", help="Specify log level: DEBUG, INFO, WARNING, ERROR, CRITICAL") parser.add_argument("--write-log", action="store_true", help="Create Logfile.") parser.add_argument("--project-path", type=str, default=os.getcwd(), help="Root path of the project to be tuned. \ @@ -27,8 +27,9 @@ def parse_args() -> AutotunerArguments: DP_EXECUTE.sh may return not 0, if either the execution or validation of the result failed. \ A third script DP_VALIDATE.sh might be added to add a validation step, where return code 0 is interpreted as a success, i.e. a valid result.") parser.add_argument("--dot-dp-path", type=str, default=os.path.join(os.getcwd(), ".discopop"), help="Path to the .discopop folder.") - parser.add_argument("--skip-cleanup", action="store_true", help="disable the deletion of created code variants. May require a lot of disk space." ) - # fmt: on + parser.add_argument("--skip-cleanup", action="store_true", help="Disable the deletion of created code variants. May require a lot of disk space." ) + parser.add_argument("--sanitize", action="store_true", help="Enable the invocation of ThreadSanitizer if DP_SANITIZE.sh is provided." ) + # fmt: is provided. arguments = parser.parse_args() @@ -38,6 +39,7 @@ def parse_args() -> AutotunerArguments: project_path=arguments.project_path, dot_dp_path=arguments.dot_dp_path, skip_cleanup=arguments.skip_cleanup, + sanitize=arguments.sanitize, )