Skip to content

Commit 6de3409

Browse files
skjernspre-commit-ci[bot]drammockautofix-ci[bot]
authored
FIX: short-circuit n_jobs=None to n_jobs=1 without Parallel setup (#13777)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Daniel McCloy <dan@mccloy.info> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
1 parent 4bd8fd5 commit 6de3409

3 files changed

Lines changed: 39 additions & 1 deletion

File tree

doc/changes/dev/13777.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Avoid some unnecessary computations when ``n_jobs=None`` is equivalent to ``n_jobs=1``, by `Simon Kern`_.

mne/parallel.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ def parallel_func(
8686
if n_jobs is not None:
8787
warn("joblib not installed. Cannot run in parallel.")
8888
n_jobs = 1
89-
if n_jobs == 1:
89+
if (n_jobs == 1) or (n_jobs is None and not _running_in_joblib_context()):
9090
n_jobs = 1
9191
my_func = func
9292
parallel = list
@@ -154,6 +154,16 @@ def parallel_progress(op_iter):
154154
return parallel_out, my_func, n_jobs
155155

156156

157+
def _running_in_joblib_context():
158+
"""Check if we are running in a joblib.parallel_config context manager."""
159+
try:
160+
from joblib.parallel import get_active_backend
161+
except ImportError:
162+
return False
163+
_, n_jobs = get_active_backend()
164+
return n_jobs is not None
165+
166+
157167
def _check_n_jobs(n_jobs):
158168
n_jobs = _ensure_int(n_jobs, "n_jobs", must_be="an int or None")
159169
if os.getenv("MNE_FORCE_SERIAL", "").lower() in ("true", "1") and n_jobs != 1:

mne/tests/test_parallel.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import multiprocessing
66
import os
7+
import sys
78
from contextlib import nullcontext
89

910
import pytest
@@ -50,3 +51,29 @@ def fun(x):
5051
with ctx:
5152
parallel, p_fun, got_jobs = parallel_func(fun, n_jobs, verbose="debug")
5253
assert got_jobs == want_jobs
54+
55+
56+
def test_parallel_func_n_jobs_none():
57+
"""Test n_jobs=None is same as n_jobs=1."""
58+
joblib = pytest.importorskip("joblib")
59+
60+
def fun(x):
61+
return x * 2
62+
63+
# test that n_jobs=None (outside context) behaves identically to n_jobs=1.
64+
parallel_none, p_fun_none, n_jobs_none = parallel_func(fun, n_jobs=None)
65+
parallel_one, p_fun_one, n_jobs_one = parallel_func(fun, n_jobs=1)
66+
67+
assert parallel_none is parallel_one is list
68+
assert n_jobs_none == n_jobs_one == 1
69+
assert p_fun_none is p_fun_one is fun, "fun should not be wrapped but is"
70+
71+
# TODO: test does not work on windows somehow, fix
72+
if sys.platform != "win32":
73+
# Test that n_jobs=None inside a joblib context uses Parallel.
74+
with joblib.parallel_config(backend="loky", n_jobs=2):
75+
parallel, p_fun, n_jobs = parallel_func(fun, n_jobs=None)
76+
77+
assert n_jobs == 2
78+
assert parallel is not list
79+
assert fun is not p_fun, "fun should be wrapped but is not"

0 commit comments

Comments
 (0)