Skip to content

Commit 967fce3

Browse files
authored
Avoid using ToolchainProfiler.profile_block. NFC (#17893)
Instead we can profile the whole function, which is actually more useful in this case for comparing the performance of EM_PYTHON_MULTIPROCESSING=1 and EM_PYTHON_MULTIPROCESSING=0. Also move the precondition checking to the top of the function and use an assert.
1 parent c12bca5 commit 967fce3

File tree

1 file changed

+64
-56
lines changed

1 file changed

+64
-56
lines changed

tools/shared.py

+64-56
Original file line numberDiff line numberDiff line change
@@ -141,18 +141,30 @@ def returncode_to_str(code):
141141
return f'returned {code}'
142142

143143

144-
# Runs multiple subprocess commands.
145-
# bool 'check': If True (default), raises an exception if any of the subprocesses failed with a nonzero exit code.
146-
# string 'route_stdout_to_temp_files_suffix': if not None, all stdouts are instead written to files, and an array of filenames is returned.
147-
# bool 'pipe_stdout': If True, an array of stdouts is returned, for each subprocess.
148144
def run_multiple_processes(commands,
149145
env=None,
150146
route_stdout_to_temp_files_suffix=None,
151147
pipe_stdout=False,
152148
check=True,
153149
cwd=None):
150+
"""Runs multiple subprocess commands.
151+
152+
check : bool
153+
If True (default), raises an exception if any of the subprocesses
154+
failed with a nonzero exit code.
155+
156+
route_stdout_to_temp_files_suffix : string
157+
if not None, all stdouts are instead written to files, and an array
158+
of filenames is returned.
159+
160+
pipe_stdout : bool
161+
If True, an array of stdouts is returned, for each subprocess.
162+
"""
163+
assert not (route_stdout_to_temp_files_suffix and pipe_stdout), 'Cannot simultaneously pipe stdout to file and a string! Choose one or the other.'
164+
154165
if env is None:
155166
env = os.environ.copy()
167+
156168
# By default, avoid using Python multiprocessing library due to a large amount of bugs it has on Windows (#8013, #718, #13785, etc.)
157169
# Use EM_PYTHON_MULTIPROCESSING=1 environment variable to enable it. It can be faster, but may not work on Windows.
158170
if int(os.getenv('EM_PYTHON_MULTIPROCESSING', '0')):
@@ -164,65 +176,61 @@ def run_multiple_processes(commands,
164176

165177
std_outs = []
166178

167-
if route_stdout_to_temp_files_suffix and pipe_stdout:
168-
raise Exception('Cannot simultaneously pipe stdout to file and a string! Choose one or the other.')
169-
170179
# TODO: Experiment with registering a signal handler here to see if that helps with Ctrl-C locking up the command prompt
171180
# when multiple child processes have been spawned.
172181
# import signal
173182
# def signal_handler(sig, frame):
174183
# sys.exit(1)
175184
# signal.signal(signal.SIGINT, signal_handler)
176185

177-
with ToolchainProfiler.profile_block('run_multiple_processes'):
178-
processes = []
179-
num_parallel_processes = get_num_cores()
180-
temp_files = get_temp_files()
181-
i = 0
182-
num_completed = 0
183-
184-
while num_completed < len(commands):
185-
if i < len(commands) and len(processes) < num_parallel_processes:
186-
# Not enough parallel processes running, spawn a new one.
187-
std_out = temp_files.get(route_stdout_to_temp_files_suffix) if route_stdout_to_temp_files_suffix else (subprocess.PIPE if pipe_stdout else None)
188-
if DEBUG:
189-
logger.debug('Running subprocess %d/%d: %s' % (i + 1, len(commands), ' '.join(commands[i])))
190-
print_compiler_stage(commands[i])
191-
processes += [(i, subprocess.Popen(commands[i], stdout=std_out, stderr=subprocess.PIPE if pipe_stdout else None, env=env, cwd=cwd))]
192-
if route_stdout_to_temp_files_suffix:
193-
std_outs += [(i, std_out.name)]
194-
i += 1
195-
else:
196-
# Not spawning a new process (Too many commands running in parallel, or no commands left): find if a process has finished.
197-
def get_finished_process():
198-
while True:
199-
j = 0
200-
while j < len(processes):
201-
if processes[j][1].poll() is not None:
202-
out, err = processes[j][1].communicate()
203-
return (j, out.decode('UTF-8') if out else '', err.decode('UTF-8') if err else '')
204-
j += 1
205-
# All processes still running; wait a short while for the first (oldest) process to finish,
206-
# then look again if any process has completed.
207-
try:
208-
out, err = processes[0][1].communicate(0.2)
209-
return (0, out.decode('UTF-8') if out else '', err.decode('UTF-8') if err else '')
210-
except subprocess.TimeoutExpired:
211-
pass
212-
213-
j, out, err = get_finished_process()
214-
idx, finished_process = processes[j]
215-
del processes[j]
216-
if pipe_stdout:
217-
std_outs += [(idx, out)]
218-
if check and finished_process.returncode != 0:
219-
if out:
220-
logger.info(out)
221-
if err:
222-
logger.error(err)
223-
224-
raise Exception('Subprocess %d/%d failed (%s)! (cmdline: %s)' % (idx + 1, len(commands), returncode_to_str(finished_process.returncode), shlex_join(commands[idx])))
225-
num_completed += 1
186+
processes = []
187+
num_parallel_processes = get_num_cores()
188+
temp_files = get_temp_files()
189+
i = 0
190+
num_completed = 0
191+
192+
while num_completed < len(commands):
193+
if i < len(commands) and len(processes) < num_parallel_processes:
194+
# Not enough parallel processes running, spawn a new one.
195+
std_out = temp_files.get(route_stdout_to_temp_files_suffix) if route_stdout_to_temp_files_suffix else (subprocess.PIPE if pipe_stdout else None)
196+
if DEBUG:
197+
logger.debug('Running subprocess %d/%d: %s' % (i + 1, len(commands), ' '.join(commands[i])))
198+
print_compiler_stage(commands[i])
199+
processes += [(i, subprocess.Popen(commands[i], stdout=std_out, stderr=subprocess.PIPE if pipe_stdout else None, env=env, cwd=cwd))]
200+
if route_stdout_to_temp_files_suffix:
201+
std_outs += [(i, std_out.name)]
202+
i += 1
203+
else:
204+
# Not spawning a new process (Too many commands running in parallel, or no commands left): find if a process has finished.
205+
def get_finished_process():
206+
while True:
207+
j = 0
208+
while j < len(processes):
209+
if processes[j][1].poll() is not None:
210+
out, err = processes[j][1].communicate()
211+
return (j, out.decode('UTF-8') if out else '', err.decode('UTF-8') if err else '')
212+
j += 1
213+
# All processes still running; wait a short while for the first (oldest) process to finish,
214+
# then look again if any process has completed.
215+
try:
216+
out, err = processes[0][1].communicate(0.2)
217+
return (0, out.decode('UTF-8') if out else '', err.decode('UTF-8') if err else '')
218+
except subprocess.TimeoutExpired:
219+
pass
220+
221+
j, out, err = get_finished_process()
222+
idx, finished_process = processes[j]
223+
del processes[j]
224+
if pipe_stdout:
225+
std_outs += [(idx, out)]
226+
if check and finished_process.returncode != 0:
227+
if out:
228+
logger.info(out)
229+
if err:
230+
logger.error(err)
231+
232+
raise Exception('Subprocess %d/%d failed (%s)! (cmdline: %s)' % (idx + 1, len(commands), returncode_to_str(finished_process.returncode), shlex_join(commands[idx])))
233+
num_completed += 1
226234

227235
# If processes finished out of order, sort the results to the order of the input.
228236
std_outs.sort(key=lambda x: x[0])

0 commit comments

Comments
 (0)