this repo has no description
at trunk 136 lines 4.3 kB view raw
1#!/usr/bin/env python3 2# Copyright (c) Facebook, Inc. and its affiliates. (http://www.facebook.com) 3import os 4import sys 5import time 6 7 8class TimeTool: 9 # Minimum number of seconds we should run benchmark before 10 # results are considered significant. 11 MIN_TIME = 0.5 12 13 # The number of runs of each benchmark. If greater than 1, the 14 # mean and standard deviation of the runs will be reported 15 REPETITIONS = 1 16 17 # Upper bound on the number of iterations 18 MAX_ITERATIONS = 1000000000 19 20 def __init__(self): 21 self.min_time = TimeTool.MIN_TIME 22 self.reps = TimeTool.REPETITIONS 23 self.max_iters = TimeTool.MAX_ITERATIONS 24 25 @staticmethod 26 def mean(numbers): 27 total = 0.0 28 for x in numbers: 29 total += x 30 return total / len(numbers) 31 32 @staticmethod 33 def stdev(numbers): 34 m = TimeTool.mean(numbers) 35 variance = 0.0 36 for x in numbers: 37 r = x - m 38 variance += r ** 2 39 variance /= len(numbers) - 1 40 return variance ** 0.5 41 42 def _should_report_results(self, iters, tm): 43 if iters >= self.max_iters or tm >= self.min_time: 44 return True 45 return False 46 47 def _do_n_iterations(self, iters): 48 total_time = 0.0 49 while iters: 50 begin = time.time() 51 self.module.run() 52 end = time.time() 53 total_time += end - begin 54 iters -= 1 55 return total_time 56 57 def _predict_num_iters(self, iters, tm): 58 # See how much iterations should be increased by. 59 # Note: Avoid division by zero with max(seconds, 1ns). 60 multiplier = self.min_time * 1.4 / max(tm, 1e-9) 61 62 # If our last run was at least 10% of self.min_time then we 63 # use the multiplier directly. 64 # Otherwise we use at most 10 times expansion. 65 # Note: When the last run was at least 10% of the min time the max 66 # expansion should be 14x. 67 is_significant = (tm / self.min_time) > 0.1 68 multiplier = multiplier if is_significant else min(10.0, multiplier) 69 if multiplier <= 1.0: 70 multiplier = 2.0 71 72 # So what seems to be the sufficiently-large iteration count? Round up. 73 max_next_iters = 0.5 + max(multiplier * iters, iters + 1.0) 74 75 # But we do have *some* sanity limits though.. 76 next_iters = min(max_next_iters, self.max_iters) 77 78 return int(next_iters) 79 80 def _do_one_repetition(self): 81 iters = 1 82 while True: 83 tm = self._do_n_iterations(iters) 84 if self._should_report_results(iters, tm): 85 break 86 iters = self._predict_num_iters(iters, tm) 87 return (tm / iters, iters) 88 89 def run(self, module): 90 self.module = module 91 tm_results = [] 92 total_iters = 0 93 repetitions = self.reps 94 while repetitions > 0: 95 tm, iters = self._do_one_repetition() 96 tm_results.append(tm) 97 total_iters += iters 98 repetitions -= 1 99 if self.reps == 1: 100 return { 101 "time_sec": tm_results[0], 102 "num_iters": total_iters, 103 "num_repetitions": self.reps, 104 } 105 return { 106 "time_sec_mean": TimeTool.mean(tm_results), 107 "time_sec_stdev": TimeTool.stdev(tm_results), 108 "num_iters": total_iters, 109 "num_repetitions": self.reps, 110 } 111 112 113def main(argv): 114 benchmark_path = os.path.realpath(sys.argv[1]) 115 directory, _, name_and_ext = benchmark_path.rpartition("/") 116 name, _, _ = name_and_ext.rpartition(".") 117 sys.path.append(directory) 118 module = __import__(name) 119 module_file = getattr(module, "__file__", "<builtin>") 120 if os.path.realpath(module_file) != benchmark_path: 121 print( 122 f"Module {name} was imported from {module_file}, not " 123 f"{benchmark_path} as expected. Does your benchmark name conflict " 124 "with a builtin module?", 125 file=sys.stderr, 126 ) 127 sys.exit(1) 128 sys.path.pop() 129 time_tool = TimeTool() 130 result = time_tool.run(module) 131 for k, v in result.items(): 132 print(k, ",", v) 133 134 135if __name__ == "__main__": 136 main(sys.argv[1:])