Skip to content

Commit ecbfbf0

Browse files
Parallelize benchmarking venv setup
1 parent 008b5e3 commit ecbfbf0

File tree

1 file changed

+56
-29
lines changed

1 file changed

+56
-29
lines changed

pyperformance/run.py

Lines changed: 56 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
import sys
55
import time
66
import traceback
7+
import concurrent.futures
8+
import io
9+
import contextlib
710

811
import pyperformance
912
from . import _utils, _python, _pythoninfo
@@ -67,47 +70,40 @@ def get_loops_from_file(filename):
6770
return loops
6871

6972

70-
def run_benchmarks(should_run, python, options):
71-
if options.same_loops is not None:
72-
loops = get_loops_from_file(options.same_loops)
73-
else:
74-
loops = {}
75-
76-
to_run = sorted(should_run)
73+
def setup_single_venv(args):
74+
(i, num_benchmarks, python, options, bench) = args
7775

78-
info = _pythoninfo.get_info(python)
79-
runid = get_run_id(info)
76+
stdout = io.StringIO()
77+
with contextlib.redirect_stdout(stdout):
78+
info = _pythoninfo.get_info(python)
79+
runid = get_run_id(info)
8080

81-
unique = getattr(options, 'unique_venvs', False)
82-
if not unique:
83-
common = VenvForBenchmarks.ensure(
84-
_venv.get_venv_root(runid.name, python=info),
85-
info,
86-
upgrade='oncreate',
87-
inherit_environ=options.inherit_environ,
88-
)
89-
90-
benchmarks = {}
91-
venvs = set()
92-
for i, bench in enumerate(to_run):
81+
unique = getattr(options, 'unique_venvs', False)
82+
if not unique:
83+
common = VenvForBenchmarks.ensure(
84+
_venv.get_venv_root(runid.name, python=info),
85+
info,
86+
upgrade='oncreate',
87+
inherit_environ=options.inherit_environ,
88+
)
9389
bench_runid = runid._replace(bench=bench)
9490
assert bench_runid.name, (bench, bench_runid)
9591
name = bench_runid.name
9692
venv_root = _venv.get_venv_root(name, python=info)
93+
bench_status = f'({i+1:>2}/{num_benchmarks})'
9794
print()
9895
print('='*50)
99-
print(f'({i+1:>2}/{len(to_run)}) creating venv for benchmark ({bench.name})')
96+
print(f'{bench_status} creating venv for benchmark ({bench.name})')
10097
print()
10198
if not unique:
102-
print('(trying common venv first)')
99+
print(f'{bench_status} (trying common venv first)')
103100
# Try the common venv first.
104101
try:
105102
common.ensure_reqs(bench)
106103
except _venv.RequirementsInstallationFailedError:
107-
print('(falling back to unique venv)')
104+
print(f'{bench_status} (falling back to unique venv)')
108105
else:
109-
benchmarks[bench] = (common, bench_runid)
110-
continue
106+
return(bench, None, common, bench_runid, stdout.getvalue())
111107
try:
112108
venv = VenvForBenchmarks.ensure(
113109
venv_root,
@@ -118,12 +114,43 @@ def run_benchmarks(should_run, python, options):
118114
# XXX Do not override when there is a requirements collision.
119115
venv.ensure_reqs(bench)
120116
except _venv.RequirementsInstallationFailedError:
121-
print('(benchmark will be skipped)')
117+
print(f'{bench_status} (benchmark will be skipped)')
122118
print()
123119
venv = None
120+
print(f'{bench_status} done')
121+
return (bench, venv_root, venv, bench_runid, stdout.getvalue())
122+
123+
def run_benchmarks(should_run, python, options):
124+
if options.same_loops is not None:
125+
loops = get_loops_from_file(options.same_loops)
126+
else:
127+
loops = {}
128+
129+
to_run = sorted(should_run)
130+
benchmarks = {}
131+
venvs = set()
132+
133+
# Setup a first venv on its own first to create common
134+
# requirements without threading issues.
135+
bench, venv_root, venv, bench_runid, cons_output = setup_single_venv((0, len(to_run), python, options, to_run[0]))
136+
if venv_root is not None:
124137
venvs.add(venv_root)
125-
benchmarks[bench] = (venv, bench_runid)
126-
print()
138+
benchmarks[bench] = (venv, bench_runid)
139+
print(cons_output)
140+
141+
# Parallelise the rest.
142+
executor_input = [(i+1, len(to_run), python, options, bench)
143+
for i, bench in enumerate(to_run[1:])]
144+
# It's fine to set a higher worker count, because this is IO-bound anyways.
145+
with concurrent.futures.ProcessPoolExecutor(max_workers=len(to_run)-1) as executor:
146+
for bench, venv_root, venv, bench_runid, cons_output in executor.map(setup_single_venv, executor_input):
147+
if venv_root is not None:
148+
venvs.add(venv_root)
149+
benchmarks[bench] = (venv, bench_runid)
150+
print(cons_output)
151+
152+
print("Completed venv installation. Now sleeping 15s to stabilize thermals.")
153+
time.sleep(15)
127154

128155
suite = None
129156
run_count = str(len(to_run))

0 commit comments

Comments
 (0)