Coverage for python/lsst/utils/threads.py: 88%
38 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-07-22 16:54 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-07-22 16:54 +0000
1# This file is part of utils.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (https://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# Use of this source code is governed by a 3-clause BSD-style
10# license that can be found in the LICENSE file.
11#
12"""Support for threading and multi-processing."""
14from __future__ import annotations
16__all__ = ["disable_implicit_threading", "set_thread_envvars"]
18import logging
19import os
20import sys
22try:
23 from threadpoolctl import threadpool_limits
24except ImportError:
25 threadpool_limits = None
27_LOG = logging.getLogger(__name__)
30def set_thread_envvars(num_threads: int = 1, override: bool = False) -> None:
31 """Set common threading environment variables to the given value.
33 Parameters
34 ----------
35 num_threads : `int`, optional
36 Number of threads to use when setting the environment variable values.
37 Default to 1 (disable threading).
38 override : `bool`, optional
39 Controls whether a previously set value should be over-ridden. Defaults
40 to `False`.
41 """
42 envvars = (
43 "OPENBLAS_NUM_THREADS",
44 "GOTO_NUM_THREADS",
45 "OMP_NUM_THREADS",
46 "MKL_NUM_THREADS",
47 "MKL_DOMAIN_NUM_THREADS",
48 "MPI_NUM_THREADS",
49 "NUMEXPR_NUM_THREADS",
50 "NUMEXPR_MAX_THREADS",
51 "NUMBA_NUM_THREADS",
52 "ARROW_IO_THREADS",
53 "VECLIB_MAXIMUM_THREADS",
54 "RAYON_NUM_THREADS",
55 "POLARS_MAX_THREADS",
56 "BLIS_NUM_THREADS",
57 )
59 for var in envvars:
60 if override or var not in os.environ:
61 os.environ[var] = str(num_threads)
63 # Also specify an explicit value for OMP_PROC_BIND to tell OpenMP not to
64 # set CPU affinity.
65 var = "OMP_PROC_BIND"
66 if override or var not in os.environ:
67 os.environ[var] = "false"
70def disable_implicit_threading() -> None:
71 """Do whatever is necessary to try to prevent implicit threading.
73 Notes
74 -----
75 Explicitly limits the number of threads allowed to be used by ``numexpr``
76 and ``pyarrow`` (if already imported) and attempts to limit the number of
77 threads in all APIs supported by the ``threadpoolctl`` package.
78 """
79 # Force one thread and force override.
80 set_thread_envvars(1, True)
82 try:
83 # This must be a deferred import since importing it immediately
84 # triggers the environment variable examination.
85 # Catch this in case numexpr is not installed.
86 import numexpr.utils
87 except ImportError:
88 pass
89 else:
90 numexpr.utils.set_num_threads(1)
92 # pyarrow sizes its thread pools from the environment only when each pool
93 # is first used, so pools that may already exist must be resized
94 # explicitly. If pyarrow has not been imported the environment variables
95 # set above are sufficient and there is no need to pay for an import here.
96 if (pyarrow := sys.modules.get("pyarrow")) is not None: 96 ↛ 108line 96 didn't jump to line 108 because the condition on line 96 was always true
97 pyarrow.set_cpu_count(1)
98 pyarrow.set_io_thread_count(1)
100 # numba records the value of NUMBA_NUM_THREADS as the maximum size of its
101 # thread pool, and numba.config.reload_config() (which numba runs on every
102 # jit compilation) raises if the environment variable no longer matches
103 # that recorded value once the pool has been launched. The number of
104 # threads actually used defaults to that recorded maximum too, so limiting
105 # an already-imported numba requires masking execution with
106 # set_num_threads in addition to keeping the recorded value and the
107 # environment variable consistent.
108 if (numba := sys.modules.get("numba")) is not None: 108 ↛ 123line 108 didn't jump to line 123 because the condition on line 108 was always true
109 try:
110 # The pool has not been launched yet, so make numba adopt the
111 # NUMBA_NUM_THREADS=1 set above before it launches.
112 numba.config.reload_config()
113 except RuntimeError:
114 # The pool is already running at a size that cannot be lowered, so
115 # numba refuses the new value. Realign NUMBA_NUM_THREADS with the
116 # launched value instead so later reloads stay consistent.
117 os.environ["NUMBA_NUM_THREADS"] = str(numba.config.NUMBA_NUM_THREADS)
118 numba.config.reload_config()
119 # Mask execution down to a single thread.
120 numba.set_num_threads(1)
122 # Try to set threads for openblas and openmp
123 if threadpool_limits is not None:
124 threadpool_limits(limits=1)
125 else:
126 _LOG.warning(
127 "threadpoolctl is not installed: thread pools of already-loaded libraries cannot be "
128 "limited and implicit threading may remain enabled."
129 )