jpayne@69
|
1 Metadata-Version: 2.1
|
jpayne@69
|
2 Name: threadpoolctl
|
jpayne@69
|
3 Version: 3.5.0
|
jpayne@69
|
4 Summary: threadpoolctl
|
jpayne@69
|
5 Home-page: https://github.com/joblib/threadpoolctl
|
jpayne@69
|
6 License: BSD-3-Clause
|
jpayne@69
|
7 Author: Thomas Moreau
|
jpayne@69
|
8 Author-email: thomas.moreau.2010@gmail.com
|
jpayne@69
|
9 Requires-Python: >=3.8
|
jpayne@69
|
10 Description-Content-Type: text/markdown
|
jpayne@69
|
11 Classifier: Intended Audience :: Developers
|
jpayne@69
|
12 Classifier: License :: OSI Approved :: BSD License
|
jpayne@69
|
13 Classifier: Programming Language :: Python :: 3
|
jpayne@69
|
14 Classifier: Programming Language :: Python :: 3.8
|
jpayne@69
|
15 Classifier: Programming Language :: Python :: 3.9
|
jpayne@69
|
16 Classifier: Programming Language :: Python :: 3.10
|
jpayne@69
|
17 Classifier: Programming Language :: Python :: 3.11
|
jpayne@69
|
18 Classifier: Programming Language :: Python :: 3.12
|
jpayne@69
|
19 Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
jpayne@69
|
20
|
jpayne@69
|
21 # Thread-pool Controls [](https://dev.azure.com/joblib/threadpoolctl/_build/latest?definitionId=1&branchName=master) [](https://codecov.io/gh/joblib/threadpoolctl)
|
jpayne@69
|
22
|
jpayne@69
|
23 Python helpers to limit the number of threads used in the
|
jpayne@69
|
24 threadpool-backed of common native libraries used for scientific
|
jpayne@69
|
25 computing and data science (e.g. BLAS and OpenMP).
|
jpayne@69
|
26
|
jpayne@69
|
27 Fine control of the underlying thread-pool size can be useful in
|
jpayne@69
|
28 workloads that involve nested parallelism so as to mitigate
|
jpayne@69
|
29 oversubscription issues.
|
jpayne@69
|
30
|
jpayne@69
|
31 ## Installation
|
jpayne@69
|
32
|
jpayne@69
|
33 - For users, install the last published version from PyPI:
|
jpayne@69
|
34
|
jpayne@69
|
35 ```bash
|
jpayne@69
|
36 pip install threadpoolctl
|
jpayne@69
|
37 ```
|
jpayne@69
|
38
|
jpayne@69
|
39 - For contributors, install from the source repository in developer
|
jpayne@69
|
40 mode:
|
jpayne@69
|
41
|
jpayne@69
|
42 ```bash
|
jpayne@69
|
43 pip install -r dev-requirements.txt
|
jpayne@69
|
44 flit install --symlink
|
jpayne@69
|
45 ```
|
jpayne@69
|
46
|
jpayne@69
|
47 then you run the tests with pytest:
|
jpayne@69
|
48
|
jpayne@69
|
49 ```bash
|
jpayne@69
|
50 pytest
|
jpayne@69
|
51 ```
|
jpayne@69
|
52
|
jpayne@69
|
53 ## Usage
|
jpayne@69
|
54
|
jpayne@69
|
55 ### Command Line Interface
|
jpayne@69
|
56
|
jpayne@69
|
57 Get a JSON description of thread-pools initialized when importing python
|
jpayne@69
|
58 packages such as numpy or scipy for instance:
|
jpayne@69
|
59
|
jpayne@69
|
60 ```
|
jpayne@69
|
61 python -m threadpoolctl -i numpy scipy.linalg
|
jpayne@69
|
62 [
|
jpayne@69
|
63 {
|
jpayne@69
|
64 "filepath": "/home/ogrisel/miniconda3/envs/tmp/lib/libmkl_rt.so",
|
jpayne@69
|
65 "prefix": "libmkl_rt",
|
jpayne@69
|
66 "user_api": "blas",
|
jpayne@69
|
67 "internal_api": "mkl",
|
jpayne@69
|
68 "version": "2019.0.4",
|
jpayne@69
|
69 "num_threads": 2,
|
jpayne@69
|
70 "threading_layer": "intel"
|
jpayne@69
|
71 },
|
jpayne@69
|
72 {
|
jpayne@69
|
73 "filepath": "/home/ogrisel/miniconda3/envs/tmp/lib/libiomp5.so",
|
jpayne@69
|
74 "prefix": "libiomp",
|
jpayne@69
|
75 "user_api": "openmp",
|
jpayne@69
|
76 "internal_api": "openmp",
|
jpayne@69
|
77 "version": null,
|
jpayne@69
|
78 "num_threads": 4
|
jpayne@69
|
79 }
|
jpayne@69
|
80 ]
|
jpayne@69
|
81 ```
|
jpayne@69
|
82
|
jpayne@69
|
83 The JSON information is written on STDOUT. If some of the packages are missing,
|
jpayne@69
|
84 a warning message is displayed on STDERR.
|
jpayne@69
|
85
|
jpayne@69
|
86 ### Python Runtime Programmatic Introspection
|
jpayne@69
|
87
|
jpayne@69
|
88 Introspect the current state of the threadpool-enabled runtime libraries
|
jpayne@69
|
89 that are loaded when importing Python packages:
|
jpayne@69
|
90
|
jpayne@69
|
91 ```python
|
jpayne@69
|
92 >>> from threadpoolctl import threadpool_info
|
jpayne@69
|
93 >>> from pprint import pprint
|
jpayne@69
|
94 >>> pprint(threadpool_info())
|
jpayne@69
|
95 []
|
jpayne@69
|
96
|
jpayne@69
|
97 >>> import numpy
|
jpayne@69
|
98 >>> pprint(threadpool_info())
|
jpayne@69
|
99 [{'filepath': '/home/ogrisel/miniconda3/envs/tmp/lib/libmkl_rt.so',
|
jpayne@69
|
100 'internal_api': 'mkl',
|
jpayne@69
|
101 'num_threads': 2,
|
jpayne@69
|
102 'prefix': 'libmkl_rt',
|
jpayne@69
|
103 'threading_layer': 'intel',
|
jpayne@69
|
104 'user_api': 'blas',
|
jpayne@69
|
105 'version': '2019.0.4'},
|
jpayne@69
|
106 {'filepath': '/home/ogrisel/miniconda3/envs/tmp/lib/libiomp5.so',
|
jpayne@69
|
107 'internal_api': 'openmp',
|
jpayne@69
|
108 'num_threads': 4,
|
jpayne@69
|
109 'prefix': 'libiomp',
|
jpayne@69
|
110 'user_api': 'openmp',
|
jpayne@69
|
111 'version': None}]
|
jpayne@69
|
112
|
jpayne@69
|
113 >>> import xgboost
|
jpayne@69
|
114 >>> pprint(threadpool_info())
|
jpayne@69
|
115 [{'filepath': '/home/ogrisel/miniconda3/envs/tmp/lib/libmkl_rt.so',
|
jpayne@69
|
116 'internal_api': 'mkl',
|
jpayne@69
|
117 'num_threads': 2,
|
jpayne@69
|
118 'prefix': 'libmkl_rt',
|
jpayne@69
|
119 'threading_layer': 'intel',
|
jpayne@69
|
120 'user_api': 'blas',
|
jpayne@69
|
121 'version': '2019.0.4'},
|
jpayne@69
|
122 {'filepath': '/home/ogrisel/miniconda3/envs/tmp/lib/libiomp5.so',
|
jpayne@69
|
123 'internal_api': 'openmp',
|
jpayne@69
|
124 'num_threads': 4,
|
jpayne@69
|
125 'prefix': 'libiomp',
|
jpayne@69
|
126 'user_api': 'openmp',
|
jpayne@69
|
127 'version': None},
|
jpayne@69
|
128 {'filepath': '/home/ogrisel/miniconda3/envs/tmp/lib/libgomp.so.1.0.0',
|
jpayne@69
|
129 'internal_api': 'openmp',
|
jpayne@69
|
130 'num_threads': 4,
|
jpayne@69
|
131 'prefix': 'libgomp',
|
jpayne@69
|
132 'user_api': 'openmp',
|
jpayne@69
|
133 'version': None}]
|
jpayne@69
|
134 ```
|
jpayne@69
|
135
|
jpayne@69
|
136 In the above example, `numpy` was installed from the default anaconda channel and comes
|
jpayne@69
|
137 with MKL and its Intel OpenMP (`libiomp5`) implementation while `xgboost` was installed
|
jpayne@69
|
138 from pypi.org and links against GNU OpenMP (`libgomp`) so both OpenMP runtimes are
|
jpayne@69
|
139 loaded in the same Python program.
|
jpayne@69
|
140
|
jpayne@69
|
141 The state of these libraries is also accessible through the object oriented API:
|
jpayne@69
|
142
|
jpayne@69
|
143 ```python
|
jpayne@69
|
144 >>> from threadpoolctl import ThreadpoolController, threadpool_info
|
jpayne@69
|
145 >>> from pprint import pprint
|
jpayne@69
|
146 >>> import numpy
|
jpayne@69
|
147 >>> controller = ThreadpoolController()
|
jpayne@69
|
148 >>> pprint(controller.info())
|
jpayne@69
|
149 [{'architecture': 'Haswell',
|
jpayne@69
|
150 'filepath': '/home/jeremie/miniconda/envs/dev/lib/libopenblasp-r0.3.17.so',
|
jpayne@69
|
151 'internal_api': 'openblas',
|
jpayne@69
|
152 'num_threads': 4,
|
jpayne@69
|
153 'prefix': 'libopenblas',
|
jpayne@69
|
154 'threading_layer': 'pthreads',
|
jpayne@69
|
155 'user_api': 'blas',
|
jpayne@69
|
156 'version': '0.3.17'}]
|
jpayne@69
|
157
|
jpayne@69
|
158 >>> controller.info() == threadpool_info()
|
jpayne@69
|
159 True
|
jpayne@69
|
160 ```
|
jpayne@69
|
161
|
jpayne@69
|
162 ### Setting the Maximum Size of Thread-Pools
|
jpayne@69
|
163
|
jpayne@69
|
164 Control the number of threads used by the underlying runtime libraries
|
jpayne@69
|
165 in specific sections of your Python program:
|
jpayne@69
|
166
|
jpayne@69
|
167 ```python
|
jpayne@69
|
168 >>> from threadpoolctl import threadpool_limits
|
jpayne@69
|
169 >>> import numpy as np
|
jpayne@69
|
170
|
jpayne@69
|
171 >>> with threadpool_limits(limits=1, user_api='blas'):
|
jpayne@69
|
172 ... # In this block, calls to blas implementation (like openblas or MKL)
|
jpayne@69
|
173 ... # will be limited to use only one thread. They can thus be used jointly
|
jpayne@69
|
174 ... # with thread-parallelism.
|
jpayne@69
|
175 ... a = np.random.randn(1000, 1000)
|
jpayne@69
|
176 ... a_squared = a @ a
|
jpayne@69
|
177 ```
|
jpayne@69
|
178
|
jpayne@69
|
179 The threadpools can also be controlled via the object oriented API, which is especially
|
jpayne@69
|
180 useful to avoid searching through all the loaded shared libraries each time. It will
|
jpayne@69
|
181 however not act on libraries loaded after the instantiation of the
|
jpayne@69
|
182 `ThreadpoolController`:
|
jpayne@69
|
183
|
jpayne@69
|
184 ```python
|
jpayne@69
|
185 >>> from threadpoolctl import ThreadpoolController
|
jpayne@69
|
186 >>> import numpy as np
|
jpayne@69
|
187 >>> controller = ThreadpoolController()
|
jpayne@69
|
188
|
jpayne@69
|
189 >>> with controller.limit(limits=1, user_api='blas'):
|
jpayne@69
|
190 ... a = np.random.randn(1000, 1000)
|
jpayne@69
|
191 ... a_squared = a @ a
|
jpayne@69
|
192 ```
|
jpayne@69
|
193
|
jpayne@69
|
194 ### Restricting the limits to the scope of a function
|
jpayne@69
|
195
|
jpayne@69
|
196 `threadpool_limits` and `ThreadpoolController` can also be used as decorators to set
|
jpayne@69
|
197 the maximum number of threads used by the supported libraries at a function level. The
|
jpayne@69
|
198 decorators are accessible through their `wrap` method:
|
jpayne@69
|
199
|
jpayne@69
|
200 ```python
|
jpayne@69
|
201 >>> from threadpoolctl import ThreadpoolController, threadpool_limits
|
jpayne@69
|
202 >>> import numpy as np
|
jpayne@69
|
203 >>> controller = ThreadpoolController()
|
jpayne@69
|
204
|
jpayne@69
|
205 >>> @controller.wrap(limits=1, user_api='blas')
|
jpayne@69
|
206 ... # or @threadpool_limits.wrap(limits=1, user_api='blas')
|
jpayne@69
|
207 ... def my_func():
|
jpayne@69
|
208 ... # Inside this function, calls to blas implementation (like openblas or MKL)
|
jpayne@69
|
209 ... # will be limited to use only one thread.
|
jpayne@69
|
210 ... a = np.random.randn(1000, 1000)
|
jpayne@69
|
211 ... a_squared = a @ a
|
jpayne@69
|
212 ...
|
jpayne@69
|
213 ```
|
jpayne@69
|
214
|
jpayne@69
|
215 ### Switching the FlexiBLAS backend
|
jpayne@69
|
216
|
jpayne@69
|
217 `FlexiBLAS` is a BLAS wrapper for which the BLAS backend can be switched at runtime.
|
jpayne@69
|
218 `threadpoolctl` exposes python bindings for this feature. Here's an example but note
|
jpayne@69
|
219 that this part of the API is experimental and subject to change without deprecation:
|
jpayne@69
|
220
|
jpayne@69
|
221 ```python
|
jpayne@69
|
222 >>> from threadpoolctl import ThreadpoolController
|
jpayne@69
|
223 >>> import numpy as np
|
jpayne@69
|
224 >>> controller = ThreadpoolController()
|
jpayne@69
|
225
|
jpayne@69
|
226 >>> controller.info()
|
jpayne@69
|
227 [{'user_api': 'blas',
|
jpayne@69
|
228 'internal_api': 'flexiblas',
|
jpayne@69
|
229 'num_threads': 1,
|
jpayne@69
|
230 'prefix': 'libflexiblas',
|
jpayne@69
|
231 'filepath': '/usr/local/lib/libflexiblas.so.3.3',
|
jpayne@69
|
232 'version': '3.3.1',
|
jpayne@69
|
233 'available_backends': ['NETLIB', 'OPENBLASPTHREAD', 'ATLAS'],
|
jpayne@69
|
234 'loaded_backends': ['NETLIB'],
|
jpayne@69
|
235 'current_backend': 'NETLIB'}]
|
jpayne@69
|
236
|
jpayne@69
|
237 # Retrieve the flexiblas controller
|
jpayne@69
|
238 >>> flexiblas_ct = controller.select(internal_api="flexiblas").lib_controllers[0]
|
jpayne@69
|
239
|
jpayne@69
|
240 # Switch the backend with one predefined at build time (listed in "available_backends")
|
jpayne@69
|
241 >>> flexiblas_ct.switch_backend("OPENBLASPTHREAD")
|
jpayne@69
|
242 >>> controller.info()
|
jpayne@69
|
243 [{'user_api': 'blas',
|
jpayne@69
|
244 'internal_api': 'flexiblas',
|
jpayne@69
|
245 'num_threads': 4,
|
jpayne@69
|
246 'prefix': 'libflexiblas',
|
jpayne@69
|
247 'filepath': '/usr/local/lib/libflexiblas.so.3.3',
|
jpayne@69
|
248 'version': '3.3.1',
|
jpayne@69
|
249 'available_backends': ['NETLIB', 'OPENBLASPTHREAD', 'ATLAS'],
|
jpayne@69
|
250 'loaded_backends': ['NETLIB', 'OPENBLASPTHREAD'],
|
jpayne@69
|
251 'current_backend': 'OPENBLASPTHREAD'},
|
jpayne@69
|
252 {'user_api': 'blas',
|
jpayne@69
|
253 'internal_api': 'openblas',
|
jpayne@69
|
254 'num_threads': 4,
|
jpayne@69
|
255 'prefix': 'libopenblas',
|
jpayne@69
|
256 'filepath': '/usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.8.so',
|
jpayne@69
|
257 'version': '0.3.8',
|
jpayne@69
|
258 'threading_layer': 'pthreads',
|
jpayne@69
|
259 'architecture': 'Haswell'}]
|
jpayne@69
|
260
|
jpayne@69
|
261 # It's also possible to directly give the path to a shared library
|
jpayne@69
|
262 >>> flexiblas_controller.switch_backend("/home/jeremie/miniforge/envs/flexiblas_threadpoolctl/lib/libmkl_rt.so")
|
jpayne@69
|
263 >>> controller.info()
|
jpayne@69
|
264 [{'user_api': 'blas',
|
jpayne@69
|
265 'internal_api': 'flexiblas',
|
jpayne@69
|
266 'num_threads': 2,
|
jpayne@69
|
267 'prefix': 'libflexiblas',
|
jpayne@69
|
268 'filepath': '/usr/local/lib/libflexiblas.so.3.3',
|
jpayne@69
|
269 'version': '3.3.1',
|
jpayne@69
|
270 'available_backends': ['NETLIB', 'OPENBLASPTHREAD', 'ATLAS'],
|
jpayne@69
|
271 'loaded_backends': ['NETLIB',
|
jpayne@69
|
272 'OPENBLASPTHREAD',
|
jpayne@69
|
273 '/home/jeremie/miniforge/envs/flexiblas_threadpoolctl/lib/libmkl_rt.so'],
|
jpayne@69
|
274 'current_backend': '/home/jeremie/miniforge/envs/flexiblas_threadpoolctl/lib/libmkl_rt.so'},
|
jpayne@69
|
275 {'user_api': 'openmp',
|
jpayne@69
|
276 'internal_api': 'openmp',
|
jpayne@69
|
277 'num_threads': 4,
|
jpayne@69
|
278 'prefix': 'libomp',
|
jpayne@69
|
279 'filepath': '/home/jeremie/miniforge/envs/flexiblas_threadpoolctl/lib/libomp.so',
|
jpayne@69
|
280 'version': None},
|
jpayne@69
|
281 {'user_api': 'blas',
|
jpayne@69
|
282 'internal_api': 'openblas',
|
jpayne@69
|
283 'num_threads': 4,
|
jpayne@69
|
284 'prefix': 'libopenblas',
|
jpayne@69
|
285 'filepath': '/usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.8.so',
|
jpayne@69
|
286 'version': '0.3.8',
|
jpayne@69
|
287 'threading_layer': 'pthreads',
|
jpayne@69
|
288 'architecture': 'Haswell'},
|
jpayne@69
|
289 {'user_api': 'blas',
|
jpayne@69
|
290 'internal_api': 'mkl',
|
jpayne@69
|
291 'num_threads': 2,
|
jpayne@69
|
292 'prefix': 'libmkl_rt',
|
jpayne@69
|
293 'filepath': '/home/jeremie/miniforge/envs/flexiblas_threadpoolctl/lib/libmkl_rt.so.2',
|
jpayne@69
|
294 'version': '2024.0-Product',
|
jpayne@69
|
295 'threading_layer': 'gnu'}]
|
jpayne@69
|
296 ```
|
jpayne@69
|
297
|
jpayne@69
|
298 You can observe that the previously linked OpenBLAS shared object stays loaded by
|
jpayne@69
|
299 the Python program indefinitely, but FlexiBLAS itself no longer delegates BLAS calls
|
jpayne@69
|
300 to OpenBLAS as indicated by the `current_backend` attribute.
|
jpayne@69
|
301 ### Writing a custom library controller
|
jpayne@69
|
302
|
jpayne@69
|
303 Currently, `threadpoolctl` has support for `OpenMP` and the main `BLAS` libraries.
|
jpayne@69
|
304 However it can also be used to control the threadpool of other native libraries,
|
jpayne@69
|
305 provided that they expose an API to get and set the limit on the number of threads.
|
jpayne@69
|
306 For that, one must implement a controller for this library and register it to
|
jpayne@69
|
307 `threadpoolctl`.
|
jpayne@69
|
308
|
jpayne@69
|
309 A custom controller must be a subclass of the `LibController` class and implement
|
jpayne@69
|
310 the attributes and methods described in the docstring of `LibController`. Then this
|
jpayne@69
|
311 new controller class must be registered using the `threadpoolctl.register` function.
|
jpayne@69
|
312 An complete example can be found [here](
|
jpayne@69
|
313 https://github.com/joblib/threadpoolctl/blob/master/tests/_pyMylib/__init__.py).
|
jpayne@69
|
314
|
jpayne@69
|
315 ### Sequential BLAS within OpenMP parallel region
|
jpayne@69
|
316
|
jpayne@69
|
317 When one wants to have sequential BLAS calls within an OpenMP parallel region, it's
|
jpayne@69
|
318 safer to set `limits="sequential_blas_under_openmp"` since setting `limits=1` and
|
jpayne@69
|
319 `user_api="blas"` might not lead to the expected behavior in some configurations
|
jpayne@69
|
320 (e.g. OpenBLAS with the OpenMP threading layer
|
jpayne@69
|
321 https://github.com/xianyi/OpenBLAS/issues/2985).
|
jpayne@69
|
322
|
jpayne@69
|
323 ### Known Limitations
|
jpayne@69
|
324
|
jpayne@69
|
325 - `threadpool_limits` can fail to limit the number of inner threads when nesting
|
jpayne@69
|
326 parallel loops managed by distinct OpenMP runtime implementations (for instance
|
jpayne@69
|
327 libgomp from GCC and libomp from clang/llvm or libiomp from ICC).
|
jpayne@69
|
328
|
jpayne@69
|
329 See the `test_openmp_nesting` function in [tests/test_threadpoolctl.py](
|
jpayne@69
|
330 https://github.com/joblib/threadpoolctl/blob/master/tests/test_threadpoolctl.py)
|
jpayne@69
|
331 for an example. More information can be found at:
|
jpayne@69
|
332 https://github.com/jeremiedbb/Nested_OpenMP
|
jpayne@69
|
333
|
jpayne@69
|
334 Note however that this problem does not happen when `threadpool_limits` is
|
jpayne@69
|
335 used to limit the number of threads used internally by BLAS calls that are
|
jpayne@69
|
336 themselves nested under OpenMP parallel loops. `threadpool_limits` works as
|
jpayne@69
|
337 expected, even if the inner BLAS implementation relies on a distinct OpenMP
|
jpayne@69
|
338 implementation.
|
jpayne@69
|
339
|
jpayne@69
|
340 - Using Intel OpenMP (ICC) and LLVM OpenMP (clang) in the same Python program
|
jpayne@69
|
341 under Linux is known to cause problems. See the following guide for more details
|
jpayne@69
|
342 and workarounds:
|
jpayne@69
|
343 https://github.com/joblib/threadpoolctl/blob/master/multiple_openmp.md
|
jpayne@69
|
344
|
jpayne@69
|
345 - Setting the maximum number of threads of the OpenMP and BLAS libraries has a global
|
jpayne@69
|
346 effect and impacts the whole Python process. There is no thread level isolation as
|
jpayne@69
|
347 these libraries do not offer thread-local APIs to configure the number of threads to
|
jpayne@69
|
348 use in nested parallel calls.
|
jpayne@69
|
349
|
jpayne@69
|
350
|
jpayne@69
|
351 ## Maintainers
|
jpayne@69
|
352
|
jpayne@69
|
353 To make a release:
|
jpayne@69
|
354
|
jpayne@69
|
355 - Bump the version number (`__version__`) in `threadpoolctl.py` and update the
|
jpayne@69
|
356 release date in `CHANGES.md`.
|
jpayne@69
|
357
|
jpayne@69
|
358 - Build the distribution archives:
|
jpayne@69
|
359
|
jpayne@69
|
360 ```bash
|
jpayne@69
|
361 pip install flit
|
jpayne@69
|
362 flit build
|
jpayne@69
|
363 ```
|
jpayne@69
|
364
|
jpayne@69
|
365 and check the contents of `dist/`.
|
jpayne@69
|
366
|
jpayne@69
|
367 - If everything is fine, make a commit for the release, tag it and push the
|
jpayne@69
|
368 tag to github:
|
jpayne@69
|
369
|
jpayne@69
|
370 ```bash
|
jpayne@69
|
371 git tag -a X.Y.Z
|
jpayne@69
|
372 git push git@github.com:joblib/threadpoolctl.git X.Y.Z
|
jpayne@69
|
373 ```
|
jpayne@69
|
374
|
jpayne@69
|
375 - Upload the wheels and source distribution to PyPI using flit. Since PyPI doesn't
|
jpayne@69
|
376 allow password authentication anymore, the username needs to be changed to the
|
jpayne@69
|
377 generic name `__token__`:
|
jpayne@69
|
378
|
jpayne@69
|
379 ```bash
|
jpayne@69
|
380 FLIT_USERNAME=__token__ flit publish
|
jpayne@69
|
381 ```
|
jpayne@69
|
382
|
jpayne@69
|
383 and a PyPI token has to be passed in place of the password.
|
jpayne@69
|
384
|
jpayne@69
|
385 - Create a PR for the release on the [conda-forge feedstock](https://github.com/conda-forge/threadpoolctl-feedstock) (or wait for the bot to make it).
|
jpayne@69
|
386
|
jpayne@69
|
387 - Publish the release on github.
|
jpayne@69
|
388
|
jpayne@69
|
389 ### Credits
|
jpayne@69
|
390
|
jpayne@69
|
391 The initial dynamic library introspection code was written by @anton-malakhov
|
jpayne@69
|
392 for the smp package available at https://github.com/IntelPython/smp .
|
jpayne@69
|
393
|
jpayne@69
|
394 threadpoolctl extends this for other operating systems. Contrary to smp,
|
jpayne@69
|
395 threadpoolctl does not attempt to limit the size of Python multiprocessing
|
jpayne@69
|
396 pools (threads or processes) or set operating system-level CPU affinity
|
jpayne@69
|
397 constraints: threadpoolctl only interacts with native libraries via their
|
jpayne@69
|
398 public runtime APIs.
|
jpayne@69
|
399
|