99 * Create a temp clone of the mypy repo for each target commit to measure
1010 * Checkout a target commit in each of the clones
1111 * Compile mypyc in each of the clones *in parallel*
12- * Create another temp clone of the mypy repo as the code to check
12+ * Create another temp clone of the first provided revision (or, with -r, a foreign repo) as the code to check
1313 * Self check with each of the compiled mypys N times
1414 * Report the average runtimes and relative performance
1515 * Remove the temp clones
@@ -44,13 +44,15 @@ def build_mypy(target_dir: str) -> None:
4444 subprocess .run (cmd , env = env , check = True , cwd = target_dir )
4545
4646
47- def clone (target_dir : str , commit : str | None ) -> None :
48- heading (f"Cloning mypy to { target_dir } " )
49- repo_dir = os .getcwd ()
47+ def clone (target_dir : str , commit : str | None , repo_source : str | None = None ) -> None :
48+ source_name = repo_source or "mypy"
49+ heading (f"Cloning { source_name } to { target_dir } " )
50+ if repo_source is None :
51+ repo_source = os .getcwd ()
5052 if os .path .isdir (target_dir ):
5153 print (f"{ target_dir } exists: deleting" )
5254 shutil .rmtree (target_dir )
53- subprocess .run (["git" , "clone" , repo_dir , target_dir ], check = True )
55+ subprocess .run (["git" , "clone" , repo_source , target_dir ], check = True )
5456 if commit :
5557 subprocess .run (["git" , "checkout" , commit ], check = True , cwd = target_dir )
5658
@@ -64,7 +66,7 @@ def edit_python_file(fnam: str) -> None:
6466
6567
6668def run_benchmark (
67- compiled_dir : str , check_dir : str , * , incremental : bool , code : str | None
69+ compiled_dir : str , check_dir : str , * , incremental : bool , code : str | None , foreign : bool | None
6870) -> float :
6971 cache_dir = os .path .join (compiled_dir , ".mypy_cache" )
7072 if os .path .isdir (cache_dir ) and not incremental :
@@ -76,6 +78,8 @@ def run_benchmark(
7678 cmd = [sys .executable , "-m" , "mypy" ]
7779 if code :
7880 cmd += ["-c" , code ]
81+ elif foreign :
82+ pass
7983 else :
8084 cmd += ["--config-file" , os .path .join (abschk , "mypy_self_check.ini" )]
8185 cmd += glob .glob (os .path .join (abschk , "mypy/*.py" ))
@@ -86,18 +90,33 @@ def run_benchmark(
8690 edit_python_file (os .path .join (abschk , "mypy/test/testcheck.py" ))
8791 t0 = time .time ()
8892 # Ignore errors, since some commits being measured may generate additional errors.
89- subprocess .run (cmd , cwd = compiled_dir , env = env )
93+ if foreign :
94+ subprocess .run (cmd , cwd = check_dir , env = env )
95+ else :
96+ subprocess .run (cmd , cwd = compiled_dir , env = env )
9097 return time .time () - t0
9198
9299
93100def main () -> None :
94- parser = argparse .ArgumentParser ()
101+ whole_program_time_0 = time .time ()
102+ parser = argparse .ArgumentParser (
103+ formatter_class = argparse .RawDescriptionHelpFormatter ,
104+ description = __doc__ ,
105+ epilog = "Remember: you usually want the first argument to this command to be 'master'." ,
106+ )
95107 parser .add_argument (
96108 "--incremental" ,
97109 default = False ,
98110 action = "store_true" ,
99111 help = "measure incremental run (fully cached)" ,
100112 )
113+ parser .add_argument (
114+ "--dont-setup" ,
115+ default = False ,
116+ action = "store_true" ,
117+ help = "don't make the clones or compile mypy, just run the performance measurement benchmark "
118+ + "(this will fail unless the clones already exist, such as from a previous run that was canceled before it deleted them)" ,
119+ )
101120 parser .add_argument (
102121 "--num-runs" ,
103122 metavar = "N" ,
@@ -112,42 +131,65 @@ def main() -> None:
112131 type = int ,
113132 help = "set maximum number of parallel builds (default=8)" ,
114133 )
134+ parser .add_argument (
135+ "-r" ,
136+ metavar = "FOREIGN_REPOSITORY" ,
137+ default = None ,
138+ type = str ,
139+ help = "measure time to typecheck the project at FOREIGN_REPOSITORY instead of mypy self-check; "
140+ + "the provided value must be the URL or path of a git repo "
141+ + "(note that this script will take no special steps to *install* the foreign repo, so you will probably get a lot of missing import errors)" ,
142+ )
115143 parser .add_argument (
116144 "-c" ,
117145 metavar = "CODE" ,
118146 default = None ,
119147 type = str ,
120148 help = "measure time to type check Python code fragment instead of mypy self-check" ,
121149 )
122- parser .add_argument ("commit" , nargs = "+" , help = "git revision to measure (e.g. branch name)" )
150+ parser .add_argument (
151+ "commit" ,
152+ nargs = "+" ,
153+ help = "git revision(s), e.g. branch name or commit id, to measure the performance of" ,
154+ )
123155 args = parser .parse_args ()
124156 incremental : bool = args .incremental
157+ dont_setup : bool = args .dont_setup
125158 commits = args .commit
126159 num_runs : int = args .num_runs + 1
127160 max_workers : int = args .j
128161 code : str | None = args .c
162+ foreign_repo : str | None = args .r
129163
130164 if not (os .path .isdir (".git" ) and os .path .isdir ("mypyc" )):
131- sys .exit ("error: Run this the mypy repo root" )
165+ sys .exit ("error: You must run this script from the mypy repo root" )
132166
133167 target_dirs = []
134168 for i , commit in enumerate (commits ):
135169 target_dir = f"mypy.{ i } .tmpdir"
136170 target_dirs .append (target_dir )
137- clone (target_dir , commit )
171+ if not dont_setup :
172+ clone (target_dir , commit )
138173
139- self_check_dir = "mypy.self.tmpdir"
140- clone (self_check_dir , commits [0 ])
174+ if foreign_repo :
175+ check_dir = "mypy.foreign.tmpdir"
176+ if not dont_setup :
177+ clone (check_dir , None , foreign_repo )
178+ else :
179+ check_dir = "mypy.self.tmpdir"
180+ if not dont_setup :
181+ clone (check_dir , commits [0 ])
141182
142- heading ("Compiling mypy" )
143- print ("(This will take a while...)" )
183+ if not dont_setup :
184+ heading ("Compiling mypy" )
185+ print ("(This will take a while...)" )
144186
145- with ThreadPoolExecutor (max_workers = max_workers ) as executor :
146- futures = [executor .submit (build_mypy , target_dir ) for target_dir in target_dirs ]
147- for future in as_completed (futures ):
148- future .result ()
187+ with ThreadPoolExecutor (max_workers = max_workers ) as executor :
188+ futures = [executor .submit (build_mypy , target_dir ) for target_dir in target_dirs ]
189+ for future in as_completed (futures ):
190+ future .result ()
149191
150- print (f"Finished compiling mypy ({ len (commits )} builds)" )
192+ print (f"Finished compiling mypy ({ len (commits )} builds)" )
151193
152194 heading ("Performing measurements" )
153195
@@ -160,7 +202,13 @@ def main() -> None:
160202 items = list (enumerate (commits ))
161203 random .shuffle (items )
162204 for i , commit in items :
163- tt = run_benchmark (target_dirs [i ], self_check_dir , incremental = incremental , code = code )
205+ tt = run_benchmark (
206+ target_dirs [i ],
207+ check_dir ,
208+ incremental = incremental ,
209+ code = code ,
210+ foreign = bool (foreign_repo ),
211+ )
164212 # Don't record the first warm-up run
165213 if n > 0 :
166214 print (f"{ commit } : t={ tt :.3f} s" )
@@ -171,15 +219,28 @@ def main() -> None:
171219 first = - 1.0
172220 for commit in commits :
173221 tt = statistics .mean (results [commit ])
222+ # pstdev (instead of stdev) is used here primarily to accommodate the case where num_runs=1
223+ s = statistics .pstdev (results [commit ]) if len (results [commit ]) > 1 else 0
174224 if first < 0 :
175225 delta = "0.0%"
176226 first = tt
177227 else :
178228 d = (tt / first ) - 1
179229 delta = f"{ d :+.1%} "
180- print (f"{ commit :<25} { tt :.3f} s ({ delta } )" )
230+ print (f"{ commit :<25} { tt :.3f} s ({ delta } ) | stdev { s :.3f} s " )
231+
232+ t = int (time .time () - whole_program_time_0 )
233+ total_time_taken_formatted = ", " .join (
234+ f"{ v } { n if v == 1 else n + 's' } "
235+ for v , n in ((t // 3600 , "hour" ), (t // 60 % 60 , "minute" ), (t % 60 , "second" ))
236+ if v
237+ )
238+ print (
239+ "Total time taken by the whole benchmarking program (including any setup):" ,
240+ total_time_taken_formatted ,
241+ )
181242
182- shutil .rmtree (self_check_dir )
243+ shutil .rmtree (check_dir )
183244 for target_dir in target_dirs :
184245 shutil .rmtree (target_dir )
185246
0 commit comments