9
9
* Create a temp clone of the mypy repo for each target commit to measure
10
10
* Checkout a target commit in each of the clones
11
11
* 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
13
13
* Self check with each of the compiled mypys N times
14
14
* Report the average runtimes and relative performance
15
15
* Remove the temp clones
@@ -44,13 +44,15 @@ def build_mypy(target_dir: str) -> None:
44
44
subprocess .run (cmd , env = env , check = True , cwd = target_dir )
45
45
46
46
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 ()
50
52
if os .path .isdir (target_dir ):
51
53
print (f"{ target_dir } exists: deleting" )
52
54
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 )
54
56
if commit :
55
57
subprocess .run (["git" , "checkout" , commit ], check = True , cwd = target_dir )
56
58
@@ -64,7 +66,7 @@ def edit_python_file(fnam: str) -> None:
64
66
65
67
66
68
def 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
68
70
) -> float :
69
71
cache_dir = os .path .join (compiled_dir , ".mypy_cache" )
70
72
if os .path .isdir (cache_dir ) and not incremental :
@@ -76,6 +78,8 @@ def run_benchmark(
76
78
cmd = [sys .executable , "-m" , "mypy" ]
77
79
if code :
78
80
cmd += ["-c" , code ]
81
+ elif foreign :
82
+ pass
79
83
else :
80
84
cmd += ["--config-file" , os .path .join (abschk , "mypy_self_check.ini" )]
81
85
cmd += glob .glob (os .path .join (abschk , "mypy/*.py" ))
@@ -86,18 +90,33 @@ def run_benchmark(
86
90
edit_python_file (os .path .join (abschk , "mypy/test/testcheck.py" ))
87
91
t0 = time .time ()
88
92
# 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 )
90
97
return time .time () - t0
91
98
92
99
93
100
def 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
+ )
95
107
parser .add_argument (
96
108
"--incremental" ,
97
109
default = False ,
98
110
action = "store_true" ,
99
111
help = "measure incremental run (fully cached)" ,
100
112
)
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
+ )
101
120
parser .add_argument (
102
121
"--num-runs" ,
103
122
metavar = "N" ,
@@ -112,42 +131,65 @@ def main() -> None:
112
131
type = int ,
113
132
help = "set maximum number of parallel builds (default=8)" ,
114
133
)
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
+ )
115
143
parser .add_argument (
116
144
"-c" ,
117
145
metavar = "CODE" ,
118
146
default = None ,
119
147
type = str ,
120
148
help = "measure time to type check Python code fragment instead of mypy self-check" ,
121
149
)
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
+ )
123
155
args = parser .parse_args ()
124
156
incremental : bool = args .incremental
157
+ dont_setup : bool = args .dont_setup
125
158
commits = args .commit
126
159
num_runs : int = args .num_runs + 1
127
160
max_workers : int = args .j
128
161
code : str | None = args .c
162
+ foreign_repo : str | None = args .r
129
163
130
164
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" )
132
166
133
167
target_dirs = []
134
168
for i , commit in enumerate (commits ):
135
169
target_dir = f"mypy.{ i } .tmpdir"
136
170
target_dirs .append (target_dir )
137
- clone (target_dir , commit )
171
+ if not dont_setup :
172
+ clone (target_dir , commit )
138
173
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 ])
141
182
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...)" )
144
186
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 ()
149
191
150
- print (f"Finished compiling mypy ({ len (commits )} builds)" )
192
+ print (f"Finished compiling mypy ({ len (commits )} builds)" )
151
193
152
194
heading ("Performing measurements" )
153
195
@@ -160,7 +202,13 @@ def main() -> None:
160
202
items = list (enumerate (commits ))
161
203
random .shuffle (items )
162
204
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
+ )
164
212
# Don't record the first warm-up run
165
213
if n > 0 :
166
214
print (f"{ commit } : t={ tt :.3f} s" )
@@ -171,15 +219,28 @@ def main() -> None:
171
219
first = - 1.0
172
220
for commit in commits :
173
221
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
174
224
if first < 0 :
175
225
delta = "0.0%"
176
226
first = tt
177
227
else :
178
228
d = (tt / first ) - 1
179
229
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
+ )
181
242
182
- shutil .rmtree (self_check_dir )
243
+ shutil .rmtree (check_dir )
183
244
for target_dir in target_dirs :
184
245
shutil .rmtree (target_dir )
185
246
0 commit comments