threadpoolctl
项目描述
线程池控制

Python辅助工具,用于限制科学计算和数据科学中使用的常用本地库(例如BLAS和OpenMP)的线程池中使用的线程数。
在涉及嵌套并行的工作负载中,精细控制底层线程池大小可以有助于缓解过度订阅问题。
安装
-
对于用户,请从 PyPI 安装最新发布的版本
pip install threadpoolctl
-
对于贡献者,请在开发者模式下从源代码仓库安装
pip install -r dev-requirements.txt flit install --symlink
然后您可以使用 pytest 运行测试
pytest
使用方法
命令行界面
获取导入 Python 包(如 numpy 或 scipy)时初始化的线程池的 JSON 描述
python -m threadpoolctl -i numpy scipy.linalg
[
{
"filepath": "/home/ogrisel/miniconda3/envs/tmp/lib/libmkl_rt.so",
"prefix": "libmkl_rt",
"user_api": "blas",
"internal_api": "mkl",
"version": "2019.0.4",
"num_threads": 2,
"threading_layer": "intel"
},
{
"filepath": "/home/ogrisel/miniconda3/envs/tmp/lib/libiomp5.so",
"prefix": "libiomp",
"user_api": "openmp",
"internal_api": "openmp",
"version": null,
"num_threads": 4
}
]
JSON 信息写入到 STDOUT。如果某些包缺失,则会在 STDERR 上显示警告信息。
Python 运行时程序化内省
内省导入 Python 包时加载的线程池启用运行时库的当前状态
>>> from threadpoolctl import threadpool_info
>>> from pprint import pprint
>>> pprint(threadpool_info())
[]
>>> import numpy
>>> pprint(threadpool_info())
[{'filepath': '/home/ogrisel/miniconda3/envs/tmp/lib/libmkl_rt.so',
'internal_api': 'mkl',
'num_threads': 2,
'prefix': 'libmkl_rt',
'threading_layer': 'intel',
'user_api': 'blas',
'version': '2019.0.4'},
{'filepath': '/home/ogrisel/miniconda3/envs/tmp/lib/libiomp5.so',
'internal_api': 'openmp',
'num_threads': 4,
'prefix': 'libiomp',
'user_api': 'openmp',
'version': None}]
>>> import xgboost
>>> pprint(threadpool_info())
[{'filepath': '/home/ogrisel/miniconda3/envs/tmp/lib/libmkl_rt.so',
'internal_api': 'mkl',
'num_threads': 2,
'prefix': 'libmkl_rt',
'threading_layer': 'intel',
'user_api': 'blas',
'version': '2019.0.4'},
{'filepath': '/home/ogrisel/miniconda3/envs/tmp/lib/libiomp5.so',
'internal_api': 'openmp',
'num_threads': 4,
'prefix': 'libiomp',
'user_api': 'openmp',
'version': None},
{'filepath': '/home/ogrisel/miniconda3/envs/tmp/lib/libgomp.so.1.0.0',
'internal_api': 'openmp',
'num_threads': 4,
'prefix': 'libgomp',
'user_api': 'openmp',
'version': None}]
在上面的示例中,numpy
是从默认的 anaconda 通道安装的,附带 MKL 和其 Intel OpenMP (libiomp5
) 实现,而xgboost
是从 pypi.org 安装的,链接到 GNU OpenMP (libgomp
),因此两个 OpenMP 运行时都加载到了同一个 Python 程序中。
这些库的状态也可以通过面向对象的 API 访问
>>> from threadpoolctl import ThreadpoolController, threadpool_info
>>> from pprint import pprint
>>> import numpy
>>> controller = ThreadpoolController()
>>> pprint(controller.info())
[{'architecture': 'Haswell',
'filepath': '/home/jeremie/miniconda/envs/dev/lib/libopenblasp-r0.3.17.so',
'internal_api': 'openblas',
'num_threads': 4,
'prefix': 'libopenblas',
'threading_layer': 'pthreads',
'user_api': 'blas',
'version': '0.3.17'}]
>>> controller.info() == threadpool_info()
True
设置线程池的最大大小
控制 Python 程序特定部分使用的底层运行时库的线程数量
>>> from threadpoolctl import threadpool_limits
>>> import numpy as np
>>> with threadpool_limits(limits=1, user_api='blas'):
... # In this block, calls to blas implementation (like openblas or MKL)
... # will be limited to use only one thread. They can thus be used jointly
... # with thread-parallelism.
... a = np.random.randn(1000, 1000)
... a_squared = a @ a
线程池也可以通过面向对象的 API 控制,这对于避免每次都搜索所有加载的共享库特别有用。然而,它不会对在 ThreadpoolController
实例化之后加载的库产生影响。
>>> from threadpoolctl import ThreadpoolController
>>> import numpy as np
>>> controller = ThreadpoolController()
>>> with controller.limit(limits=1, user_api='blas'):
... a = np.random.randn(1000, 1000)
... a_squared = a @ a
限制到函数的作用域
threadpool_limits
和 ThreadpoolController
也可以用作装饰器,在函数级别设置支持的库使用的最大线程数。装饰器可以通过它们的 wrap
方法访问
>>> from threadpoolctl import ThreadpoolController, threadpool_limits
>>> import numpy as np
>>> controller = ThreadpoolController()
>>> @controller.wrap(limits=1, user_api='blas')
... # or @threadpool_limits.wrap(limits=1, user_api='blas')
... def my_func():
... # Inside this function, calls to blas implementation (like openblas or MKL)
... # will be limited to use only one thread.
... a = np.random.randn(1000, 1000)
... a_squared = a @ a
...
切换 FlexiBLAS 后端
FlexiBLAS
是一个 BLAS 包装器,其 BLAS 后端可以在运行时切换。threadpoolctl
提供了此功能的 Python 绑定。以下是一个示例,但请注意,此部分的 API 是实验性的,可能会更改而无需弃用
>>> from threadpoolctl import ThreadpoolController
>>> import numpy as np
>>> controller = ThreadpoolController()
>>> controller.info()
[{'user_api': 'blas',
'internal_api': 'flexiblas',
'num_threads': 1,
'prefix': 'libflexiblas',
'filepath': '/usr/local/lib/libflexiblas.so.3.3',
'version': '3.3.1',
'available_backends': ['NETLIB', 'OPENBLASPTHREAD', 'ATLAS'],
'loaded_backends': ['NETLIB'],
'current_backend': 'NETLIB'}]
# Retrieve the flexiblas controller
>>> flexiblas_ct = controller.select(internal_api="flexiblas").lib_controllers[0]
# Switch the backend with one predefined at build time (listed in "available_backends")
>>> flexiblas_ct.switch_backend("OPENBLASPTHREAD")
>>> controller.info()
[{'user_api': 'blas',
'internal_api': 'flexiblas',
'num_threads': 4,
'prefix': 'libflexiblas',
'filepath': '/usr/local/lib/libflexiblas.so.3.3',
'version': '3.3.1',
'available_backends': ['NETLIB', 'OPENBLASPTHREAD', 'ATLAS'],
'loaded_backends': ['NETLIB', 'OPENBLASPTHREAD'],
'current_backend': 'OPENBLASPTHREAD'},
{'user_api': 'blas',
'internal_api': 'openblas',
'num_threads': 4,
'prefix': 'libopenblas',
'filepath': '/usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.8.so',
'version': '0.3.8',
'threading_layer': 'pthreads',
'architecture': 'Haswell'}]
# It's also possible to directly give the path to a shared library
>>> flexiblas_controller.switch_backend("/home/jeremie/miniforge/envs/flexiblas_threadpoolctl/lib/libmkl_rt.so")
>>> controller.info()
[{'user_api': 'blas',
'internal_api': 'flexiblas',
'num_threads': 2,
'prefix': 'libflexiblas',
'filepath': '/usr/local/lib/libflexiblas.so.3.3',
'version': '3.3.1',
'available_backends': ['NETLIB', 'OPENBLASPTHREAD', 'ATLAS'],
'loaded_backends': ['NETLIB',
'OPENBLASPTHREAD',
'/home/jeremie/miniforge/envs/flexiblas_threadpoolctl/lib/libmkl_rt.so'],
'current_backend': '/home/jeremie/miniforge/envs/flexiblas_threadpoolctl/lib/libmkl_rt.so'},
{'user_api': 'openmp',
'internal_api': 'openmp',
'num_threads': 4,
'prefix': 'libomp',
'filepath': '/home/jeremie/miniforge/envs/flexiblas_threadpoolctl/lib/libomp.so',
'version': None},
{'user_api': 'blas',
'internal_api': 'openblas',
'num_threads': 4,
'prefix': 'libopenblas',
'filepath': '/usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.8.so',
'version': '0.3.8',
'threading_layer': 'pthreads',
'architecture': 'Haswell'},
{'user_api': 'blas',
'internal_api': 'mkl',
'num_threads': 2,
'prefix': 'libmkl_rt',
'filepath': '/home/jeremie/miniforge/envs/flexiblas_threadpoolctl/lib/libmkl_rt.so.2',
'version': '2024.0-Product',
'threading_layer': 'gnu'}]
您可以看到,之前链接的 OpenBLAS 共享对象会无限期地由 Python 程序加载,但 FlexiBLAS 本身不再将 BLAS 调用委托给 OpenBLAS,如 current_backend
属性所示。
编写自定义库控制器
目前,threadpoolctl
支持 OpenMP
和主要的 BLAS
库。但是,它也可以用来控制其他原生库的线程池,前提是它们提供了获取和设置线程数量限制的 API。为此,必须为此库实现一个控制器并将其注册到 threadpoolctl
。
自定义控制器必须是 LibController
类的子类,并实现 LibController
文档字符串中描述的属性和方法。然后必须使用 threadpoolctl.register
函数注册此新控制器类。一个完整的示例可以在这里找到。
OpenMP 并行区域内的顺序 BLAS
当在 OpenMP 并行区域内部希望有顺序的 BLAS 调用时,设置 limits="sequential_blas_under_openmp"
更安全,因为设置 limits=1
和 user_api="blas"
在某些配置中可能不会产生预期的行为(例如,OpenBLAS 与 OpenMP 线程层 https://github.com/xianyi/OpenBLAS/issues/2985)。
已知限制
-
当嵌套由不同的 OpenMP 运行时实现管理的并行循环(例如 GCC 的 libgomp 和 clang/llvm 的 libomp 或 ICC 的 libiomp)时,
threadpool_limits
可能无法限制内部线程的数量。请参阅
test_openmp_nesting
函数,该函数位于 tests/test_threadpoolctl.py 中,以获取示例。更多信息可以在以下链接找到:https://github.com/jeremiedbb/Nested_OpenMP但是请注意,当使用
threadpool_limits
限制由 OpenMP 并行循环嵌套的 BLAS 调用内部使用的线程数时,这个问题不会发生。threadpool_limits
的工作方式符合预期,即使内部 BLAS 实现依赖于不同的 OpenMP 实现。 -
在 Linux 下,同一 Python 程序中使用 Intel OpenMP (ICC) 和 LLVM OpenMP (clang) 已知会导致问题。有关更多详细信息和工作方案,请参阅以下指南:https://github.com/joblib/threadpoolctl/blob/master/multiple_openmp.md
-
设置 OpenMP 和 BLAS 库的最大线程数具有全局影响,并影响整个 Python 进程。由于这些库不提供线程本地 API 来配置嵌套并行调用中使用的线程数,因此没有线程级别的隔离。
维护者
要发布版本
-
在
threadpoolctl.py
中增加版本号 (__version__
) 并更新CHANGES.md
中的发布日期。 -
构建分发存档
pip install flit
flit build
并检查 dist/
的内容。
- 如果一切正常,为发布创建一个提交,标记它并将标签推送到 github
git tag -a X.Y.Z
git push git@github.com:joblib/threadpoolctl.git X.Y.Z
- 使用 flit 将轮和源分发上传到 PyPI。由于 PyPI 不再允许密码身份验证,用户名需要更改为通用名称
__token__
FLIT_USERNAME=__token__ flit publish
并且需要一个 PyPI 令牌代替密码。
-
在 conda-forge 食品库 上创建一个用于发布的 PR(或等待机器人创建)。
-
在 github 上发布发布。
致谢
最初的动态库反射代码由 @anton-malakhov 为 https://github.com/IntelPython/smp 上的 smp 包编写。
threadpoolctl 为其他操作系统扩展了此功能。与 smp 不同,threadpoolctl 不尝试限制 Python 多进程池(线程或进程)的大小或设置操作系统级别的 CPU 亲和力约束:threadpoolctl 只通过其公共运行时 API 与本地库交互。
项目详情
下载文件
下载您平台的文件。如果您不确定选择哪个,请了解更多关于 安装包 的信息。
源分发
构建分发
threadpoolctl-3.5.0.tar.gz 的散列
算法 | 散列摘要 | |
---|---|---|
SHA256 | 082433502dd922bf738de0d8bcc4fdcbf0979ff44c42bd40f5af8a282f6fa107 |
|
MD5 | 3ae140ae3bb08f43fdc19db73bcdf478 |
|
BLAKE2b-256 | bd55b5148dcbf72f5cde221f8bfe3b6a540da7aa1842f6b491ad979a6c8b84af |
threadpoolctl-3.5.0-py3-none-any.whl 的哈希值
算法 | 散列摘要 | |
---|---|---|
SHA256 | 56c1e26c150397e58c4926da8eeee87533b1e32bef131bd4bf6a2f45f3185467 |
|
MD5 | b2025a5a76bf58e86d8c6be2fd43ddd4 |
|
BLAKE2b-256 | 4b2cffbf7a134b9ab11a67b0cf0726453cedd9c5043a4fe7a35d1cefa9a1bcfb |