Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions docs/source/reference/envvars.rst
Original file line number Diff line number Diff line change
Expand Up @@ -527,3 +527,14 @@ Threading Control
* ``tbb`` - A threading layer backed by Intel TBB.
* ``omp`` - A threading layer backed by OpenMP.
* ``workqueue`` - A simple built-in work-sharing task scheduler.

.. envvar:: NUMBA_THREADING_LAYER_PRIORITY

This environment variable controls the order in which the libraries used for
concurrent execution, for the CPU parallel targets
(``@vectorize(target='parallel')``, ``@guvectorize(target='parallel')``
and ``@njit(parallel=True)``), are prioritized for use. The variable type is
string and by default is ``tbb omp workqueue``, with the priority taken based
on position from the left of the string, left most being the highest. Valid
values are any permutation of the three choices (for more information about
these see :ref:`the threading layer documentation <numba-threading-layer>`.)
19 changes: 19 additions & 0 deletions docs/source/user/threading-layer.rst
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,25 @@ choosing a threading layer, the first is by selecting a threading layer that is
safe under various forms of parallel execution, the second is through explicit
selection via the threading layer name (e.g. ``tbb``).

Setting the threading layer selection priority
----------------------------------------------

By default the threading layers are searched in the order of ``'tbb'``,
``'omp'``, then ``'workqueue'``. To change this search order whilst
maintaining the selection of a threading layer based on availability, the
environment variable :envvar:`NUMBA_THREADING_LAYER_PRIORITY` can be used.

Note that it can also be set via
:py:data:`numba.config.THREADING_LAYER_PRIORITY`.
Similar to :py:data:`numba.config.THREADING_LAYER`,
it must occur logically before any Numba based
compilation for a parallel target has occurred.

For example, to instruct Numba to choose ``omp`` first if available,
then ``tbb`` and so on, set the environment variable as
``NUMBA_THREADING_LAYER_PRIORITY="omp tbb workqueue"``.
Or programmatically,
``numba.config.THREADING_LAYER_PRIORITY = ["omp", "tbb", "workqueue"]``.

Selecting a threading layer for safe parallel execution
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Expand Down
5 changes: 5 additions & 0 deletions numba/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,11 @@ def avx_default():
DISABLE_JIT = _readenv("NUMBA_DISABLE_JIT", int, 0)

# choose parallel backend to use

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment does not match this variable, it belongs to THREADING_LAYER.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But shouldn't both THREADING_LAYER_PRIORITY and THREADING_LAYER are part of the "choose parallel backend to use" process?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that the THREADING_LAYER, if prescribed, will act as the choice for the parallel backend in use, whereas the THREADING_LAYER_PRIORITY sets preference for use of threading layers in the case that no explicit choice is prescribed?

THREADING_LAYER_PRIORITY = _readenv(
"NUMBA_THREADING_LAYER_PRIORITY",
lambda string: string.split(),
['tbb', 'omp', 'workqueue'],
)
Comment thread
ickc marked this conversation as resolved.
THREADING_LAYER = _readenv("NUMBA_THREADING_LAYER", str, 'default')

# CUDA Configs
Expand Down
10 changes: 9 additions & 1 deletion numba/np/ufunc/parallel.py
Original file line number Diff line number Diff line change
Expand Up @@ -422,7 +422,15 @@ def select_from_backends(backends):
return lib, backend

t = str(config.THREADING_LAYER).lower()
namedbackends = ['tbb', 'omp', 'workqueue']
namedbackends = config.THREADING_LAYER_PRIORITY
if not (len(namedbackends) == 3 and
set(namedbackends) == {'tbb', 'omp', 'workqueue'}):
raise ValueError(
"THREADING_LAYER_PRIORITY invalid: %s. "
"It must be a permutation of "
"{'tbb', 'omp', 'workqueue'}"
% namedbackends
)

lib = None
err_helpers = dict()
Expand Down
75 changes: 75 additions & 0 deletions numba/tests/test_parallel_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
Tests the parallel backend
"""
import faulthandler
import itertools
import multiprocessing
import os
import random
Expand Down Expand Up @@ -521,6 +522,80 @@ def generate(cls):
TestThreadingLayerSelection.generate()


@skip_parfors_unsupported
class TestThreadingLayerPriority(ThreadLayerTestHelper):

def each_env_var(self, env_var: str):
"""Test setting priority via env var NUMBA_THREADING_LAYER_PRIORITY.

:return: threading_layer_priority, stderr
(containing ``@threading_layer@``)
"""
env = os.environ.copy()
env['NUMBA_THREADING_LAYER'] = 'default'
env['NUMBA_THREADING_LAYER_PRIORITY'] = env_var

code = """import sys
import numba

# trigger threading layer decision
# hence catching invalid THREADING_LAYER_PRIORITY
@numba.jit(
'float64[::1](float64[::1], float64[::1])',
nopython=True,
parallel=True,
)
def plus(x, y):
return x + y

print(' '.join(numba.config.THREADING_LAYER_PRIORITY))
print("@%s@" % numba.threading_layer(), file=sys.stderr)
Comment on lines +551 to +552

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of using stdout/stderr, perhaps just make these into assertions such that the process will exit non-zero if there's a problem. The use of stdout/stderr relies on these streams never having unexpected things written to them (e.g. a deprecation warning) which makes their use more fragile.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The stdout should be safe. The stderr pattern @%s@ is actually copied from other tests in Numba (if you search this string in the same file there are 2 other such uses.) So I was just following whatever is practicing in Numba already...

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The stdout should be safe. The stderr pattern @%s@ is actually copied from other tests in Numba (if you search this string in the same file there are 2 other such uses.) So I was just following whatever is practicing in Numba already...

This is one of the things the code base has been moving away from as it's a little error prone. As it's variables that are ending up as strings to test, having an assert on the actual variables is cheap and reduces maintenance burden. In the interests of getting this feature merged and pre-existing use of this pattern I'm included to leave it. I'll make a note of this pattern as something to refactor generally.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#7458 tracks.

"""
cmd = [
sys.executable,
'-c',
code,
]
return self.run_cmd(cmd, env=env)

def test_valid_env_var(self):
default = ['tbb', 'omp', 'workqueue']
for p in itertools.permutations(default):
env_var = ' '.join(p)
threading_layer_priority, _ = self.each_env_var(env_var)
self.assertEqual(threading_layer_priority.strip(), env_var)

def test_invalid_env_var(self):
env_var = 'tbb omp workqueue notvalidhere'
with self.assertRaises(AssertionError) as raises:
self.each_env_var(env_var)
for msg in (
"THREADING_LAYER_PRIORITY invalid:",
"It must be a permutation of"
):
self.assertIn(f"{msg}", str(raises.exception))

@skip_no_omp
def test_omp(self):
for env_var in ("omp tbb workqueue", "omp workqueue tbb"):
threading_layer_priority, out = self.each_env_var(env_var)
self.assertEqual(threading_layer_priority.strip(), env_var)
self.assertIn("@omp@", out)

@skip_no_tbb
def test_tbb(self):
for env_var in ("tbb omp workqueue", "tbb workqueue omp"):
threading_layer_priority, out = self.each_env_var(env_var)
self.assertEqual(threading_layer_priority.strip(), env_var)
self.assertIn("@tbb@", out)

def test_workqueue(self):
for env_var in ("workqueue tbb omp", "workqueue omp tbb"):
threading_layer_priority, out = self.each_env_var(env_var)
self.assertEqual(threading_layer_priority.strip(), env_var)
self.assertIn("@workqueue@", out)


@skip_parfors_unsupported
class TestMiscBackendIssues(ThreadLayerTestHelper):
"""
Expand Down