Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-threadpoolctl for openSUSE:Factory checked in at 2023-12-09 22:49:32 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-threadpoolctl (Old) and /work/SRC/openSUSE:Factory/.python-threadpoolctl.new.25432 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-threadpoolctl" Sat Dec 9 22:49:32 2023 rev:8 rq:1132143 version:3.2.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-threadpoolctl/python-threadpoolctl.changes 2023-06-12 15:24:44.714446355 +0200 +++ /work/SRC/openSUSE:Factory/.python-threadpoolctl.new.25432/python-threadpoolctl.changes 2023-12-09 22:49:56.892354323 +0100 @@ -1,0 +2,10 @@ +Fri Dec 8 16:25:46 UTC 2023 - Dirk Müller <dmuel...@suse.com> + +- update to 3.2.0: + * Dropped support for Python 3.6 and 3.7. + * Added support for custom library controllers. Custom + controllers must inherit from the `threadpoolctl.LibController` + class and be registered to threadpoolctl using the + `threadpoolctl.register` function. + +------------------------------------------------------------------- Old: ---- threadpoolctl-3.1.0.tar.gz New: ---- threadpoolctl-3.2.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-threadpoolctl.spec ++++++ --- /var/tmp/diff_new_pack.glUx5z/_old 2023-12-09 22:49:57.524377141 +0100 +++ /var/tmp/diff_new_pack.glUx5z/_new 2023-12-09 22:49:57.524377141 +0100 @@ -15,16 +15,17 @@ # Please submit bugfixes or comments via https://bugs.opensuse.org/ # + %{?sle15_python_module_pythons} Name: python-threadpoolctl -Version: 3.1.0 +Version: 3.2.0 Release: 0 Summary: Thread-pool Controls License: BSD-3-Clause Group: Development/Languages/Python URL: https://github.com/joblib/threadpoolctl Source: https://files.pythonhosted.org/packages/source/t/threadpoolctl/threadpoolctl-%{version}.tar.gz -BuildRequires: %{python_module devel} +BuildRequires: %{python_module devel >= 3.8} BuildRequires: %{python_module flit-core} BuildRequires: %{python_module numpy} BuildRequires: %{python_module pip} ++++++ threadpoolctl-3.1.0.tar.gz -> threadpoolctl-3.2.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/threadpoolctl-3.1.0/.azure_pipeline.yml new/threadpoolctl-3.2.0/.azure_pipeline.yml --- old/threadpoolctl-3.1.0/.azure_pipeline.yml 2022-01-31 16:08:24.277708800 +0100 +++ new/threadpoolctl-3.2.0/.azure_pipeline.yml 2023-07-13 16:19:36.077411700 +0200 @@ -18,6 +18,21 @@ - stage: jobs: + - job: 'linting' + displayName: Linting + pool: + vmImage: ubuntu-latest + steps: + - task: UsePythonVersion@0 + inputs: + versionSpec: '3.11' + - script: | + pip install black + displayName: install black + - script: | + black --check --diff . + displayName: Run black + - template: continuous_integration/windows.yml parameters: name: Windows @@ -27,15 +42,15 @@ VERSION_PYTHON: '*' PACKAGER: 'conda-forge' BLAS: 'mkl' - py39_conda_forge_openblas: - VERSION_PYTHON: '3.9' + py310_conda_forge_openblas: + VERSION_PYTHON: '3.10' PACKAGER: 'conda-forge' BLAS: 'openblas' - py37_conda: - VERSION_PYTHON: '3.7' + py39_conda: + VERSION_PYTHON: '3.9' PACKAGER: 'conda' - py36_pip: - VERSION_PYTHON: '3.6' + py38_pip: + VERSION_PYTHON: '3.8' PACKAGER: 'pip' @@ -52,16 +67,16 @@ VERSION_PYTHON: '3.8' CC_OUTER_LOOP: 'gcc' CC_INNER_LOOP: 'gcc' - py36_ubuntu_openblas_gcc_gcc: + py38_ubuntu_openblas_gcc_gcc: PACKAGER: 'ubuntu' APT_BLAS: 'libopenblas-base libopenblas-dev' VERSION_PYTHON: '3.8' CC_OUTER_LOOP: 'gcc' CC_INNER_LOOP: 'gcc' - # Linux + Python 3.7 and homogeneous runtime nesting. - py37_conda_openblas_clang_clang: + # Linux + Python 3.9 and homogeneous runtime nesting. + py39_conda_openblas_clang_clang: PACKAGER: 'conda' - VERSION_PYTHON: '3.7' + VERSION_PYTHON: '3.9' BLAS: 'openblas' CC_OUTER_LOOP: 'clang-10' CC_INNER_LOOP: 'clang-10' @@ -97,7 +112,6 @@ OPENBLAS_THREADING_LAYER: 'openmp' CC_OUTER_LOOP: 'gcc' CC_INNER_LOOP: 'gcc' - LINT: 'true' # Linux environment with no numpy and heterogeneous OpenMP runtimes. pylatest_conda_nonumpy_gcc_clang: PACKAGER: 'conda' @@ -137,16 +151,24 @@ - template: continuous_integration/posix.yml parameters: name: macOS - vmImage: macOS-10.15 + vmImage: macOS-11 matrix: # MacOS environment with OpenMP installed through homebrew - py36_conda_homebrew_libomp: + py38_conda_homebrew_libomp: PACKAGER: 'conda' - VERSION_PYTHON: '3.6' + VERSION_PYTHON: '3.8' BLAS: 'openblas' CC_OUTER_LOOP: 'clang' CC_INNER_LOOP: 'clang' INSTALL_LIBOMP: 'homebrew' + # MacOS env with OpenBLAS and OpenMP installed through conda-forge compilers + py39_conda_forge_clang_openblas: + PACKAGER: 'conda-forge' + VERSION_PYTHON: '*' + BLAS: 'openblas' + CC_OUTER_LOOP: 'clang' + CC_INNER_LOOP: 'clang' + INSTALL_LIBOMP: 'conda-forge' # MacOS environment with OpenMP installed through conda-forge compilers pylatest_conda_forge_clang: PACKAGER: 'conda-forge' @@ -164,7 +186,7 @@ - job: 'no_test_always_skipped' displayName: 'No test always skipped' pool: - vmImage: ubuntu-20.04 + vmImage: ubuntu-latest steps: - download: current - script: | diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/threadpoolctl-3.1.0/.gitignore new/threadpoolctl-3.2.0/.gitignore --- old/threadpoolctl-3.1.0/.gitignore 2019-09-16 14:06:34.688814200 +0200 +++ new/threadpoolctl-3.2.0/.gitignore 2023-07-12 10:17:49.739681000 +0200 @@ -1,11 +1,14 @@ -# Python and Cython generated files +# Python generated files *.pyc __pycache__ .cache .pytest_cache + +# Cython/C generated files +*.o *.so *.dylib -*.c +tests/_openmp_test_helper/*.c # Python install files, build and release artifacts *.egg-info/ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/threadpoolctl-3.1.0/CHANGES.md new/threadpoolctl-3.2.0/CHANGES.md --- old/threadpoolctl-3.1.0/CHANGES.md 2022-01-31 17:25:14.640525300 +0100 +++ new/threadpoolctl-3.2.0/CHANGES.md 2023-07-13 16:44:13.625080800 +0200 @@ -1,3 +1,18 @@ +3.2.0 (2023-07-13) +================== + +- Dropped support for Python 3.6 and 3.7. + +- Added support for custom library controllers. Custom controllers must inherit from + the `threadpoolctl.LibController` class and be registered to threadpoolctl using the + `threadpoolctl.register` function. + https://github.com/joblib/threadpoolctl/pull/138 + +- A warning is raised on macOS when threadpoolctl finds both Intel OpenMP and LLVM + OpenMP runtimes loaded simultaneously by the same Python program. See details and + workarounds at https://github.com/joblib/threadpoolctl/blob/master/multiple_openmp.md. + https://github.com/joblib/threadpoolctl/pull/142 + 3.1.0 (2022-01-31) ================== diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/threadpoolctl-3.1.0/PKG-INFO new/threadpoolctl-3.2.0/PKG-INFO --- old/threadpoolctl-3.1.0/PKG-INFO 1970-01-01 01:00:00.000000000 +0100 +++ new/threadpoolctl-3.2.0/PKG-INFO 1970-01-01 01:00:00.000000000 +0100 @@ -1,20 +1,20 @@ Metadata-Version: 2.1 Name: threadpoolctl -Version: 3.1.0 +Version: 3.2.0 Summary: threadpoolctl Home-page: https://github.com/joblib/threadpoolctl License: BSD-3-Clause Author: Thomas Moreau Author-email: thomas.moreau.2...@gmail.com -Requires-Python: >=3.6 +Requires-Python: >=3.8 Description-Content-Type: text/markdown Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.6 -Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 Classifier: Topic :: Software Development :: Libraries :: Python Modules # Thread-pool Controls [](https://dev.azure.com/joblib/threadpoolctl/_build/latest?definitionId=1&branchName=master) [](https://codecov.io/gh/joblib/threadpoolctl) @@ -211,10 +211,25 @@ ... ``` +### Writing a custom library controller + +Currently, `threadpoolctl` has support for `OpenMP` and the main `BLAS` libraries. +However it can also be used to control the threadpool of other native libraries, +provided that they expose an API to get and set the limit on the number of threads. +For that, one must implement a controller for this library and register it to +`threadpoolctl`. + +A custom controller must be a subclass of the `LibController` class and implement +the attributes and methods described in the docstring of `LibController`. Then this +new controller class must be registered using the `threadpoolctl.register` function. +An complete example can be found [here]( + https://github.com/joblib/threadpoolctl/blob/master/tests/_pyMylib/__init__.py). + ### Sequential BLAS within OpenMP parallel region When one wants to have sequential BLAS calls within an OpenMP parallel region, it's -safer to set `limits="sequential_blas_under_openmp"` since setting `limits=1` and `user_api="blas"` might not lead to the expected behavior in some configurations +safer to set `limits="sequential_blas_under_openmp"` since setting `limits=1` and +`user_api="blas"` might not lead to the expected behavior in some configurations (e.g. OpenBLAS with the OpenMP threading layer https://github.com/xianyi/OpenBLAS/issues/2985). diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/threadpoolctl-3.1.0/README.md new/threadpoolctl-3.2.0/README.md --- old/threadpoolctl-3.1.0/README.md 2022-01-31 16:08:24.277708800 +0100 +++ new/threadpoolctl-3.2.0/README.md 2023-07-12 10:17:49.739681000 +0200 @@ -192,10 +192,25 @@ ... ``` +### Writing a custom library controller + +Currently, `threadpoolctl` has support for `OpenMP` and the main `BLAS` libraries. +However it can also be used to control the threadpool of other native libraries, +provided that they expose an API to get and set the limit on the number of threads. +For that, one must implement a controller for this library and register it to +`threadpoolctl`. + +A custom controller must be a subclass of the `LibController` class and implement +the attributes and methods described in the docstring of `LibController`. Then this +new controller class must be registered using the `threadpoolctl.register` function. +An complete example can be found [here]( + https://github.com/joblib/threadpoolctl/blob/master/tests/_pyMylib/__init__.py). + ### Sequential BLAS within OpenMP parallel region When one wants to have sequential BLAS calls within an OpenMP parallel region, it's -safer to set `limits="sequential_blas_under_openmp"` since setting `limits=1` and `user_api="blas"` might not lead to the expected behavior in some configurations +safer to set `limits="sequential_blas_under_openmp"` since setting `limits=1` and +`user_api="blas"` might not lead to the expected behavior in some configurations (e.g. OpenBLAS with the OpenMP threading layer https://github.com/xianyi/OpenBLAS/issues/2985). diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/threadpoolctl-3.1.0/continuous_integration/build_test_ext.sh new/threadpoolctl-3.2.0/continuous_integration/build_test_ext.sh --- old/threadpoolctl-3.1.0/continuous_integration/build_test_ext.sh 2019-06-26 11:41:29.299105200 +0200 +++ new/threadpoolctl-3.2.0/continuous_integration/build_test_ext.sh 2023-07-12 10:17:49.739681000 +0200 @@ -2,6 +2,14 @@ set -e +if [[ "$OSTYPE" == "linux-gnu"* ]]; then + pushd tests/_pyMylib + rm -rf *.so *.o + gcc -c -Wall -Werror -fpic -o my_threaded_lib.o my_threaded_lib.c + gcc -shared -o my_threaded_lib.so my_threaded_lib.o + popd +fi + pushd tests/_openmp_test_helper rm -rf *.c *.so *.dylib build/ python setup_inner.py build_ext -i diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/threadpoolctl-3.1.0/continuous_integration/check_no_test_skipped.py new/threadpoolctl-3.2.0/continuous_integration/check_no_test_skipped.py --- old/threadpoolctl-3.1.0/continuous_integration/check_no_test_skipped.py 2021-09-17 11:26:34.308249000 +0200 +++ new/threadpoolctl-3.2.0/continuous_integration/check_no_test_skipped.py 2023-06-30 17:31:34.528461200 +0200 @@ -33,11 +33,20 @@ always_skipped[test_name] = skipped print("\n------------------------------------------------------------------\n") + +# List of tests that we don't want to fail the CI if they are skipped in +# every job. This is useful for tests that depend on specific versions of +# numpy or scipy and we don't want to pin old versions of these libraries. +SAFE_SKIPPED_TESTS = ["test_multiple_shipped_openblas"] + fail = False for test, skipped in always_skipped.items(): if skipped: - fail = True - print(test, "was skipped in every job") + if test in SAFE_SKIPPED_TESTS: + print(test, "was skipped in every job but it's fine to skip it") + else: + fail = True + print(test, "was skipped in every job") if fail: sys.exit(1) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/threadpoolctl-3.1.0/continuous_integration/install_with_blis.sh new/threadpoolctl-3.2.0/continuous_integration/install_with_blis.sh --- old/threadpoolctl-3.1.0/continuous_integration/install_with_blis.sh 2022-01-20 18:05:26.048133900 +0100 +++ new/threadpoolctl-3.2.0/continuous_integration/install_with_blis.sh 2023-07-12 15:09:55.561690000 +0200 @@ -13,7 +13,7 @@ sudo apt-get install libomp-dev # create conda env -conda create -n $VIRTUALENV -q --yes python=$VERSION_PYTHON pip cython +conda create -n $VIRTUALENV -q --yes -c conda-forge python=$VERSION_PYTHON pip cython source activate $VIRTUALENV if [[ "$BLIS_CC" == "gcc-8" ]]; then @@ -41,8 +41,7 @@ library_dirs = $ABS_PATH/BLIS_install/lib include_dirs = $ABS_PATH/BLIS_install/include/blis runtime_library_dirs = $ABS_PATH/BLIS_install/lib" > site.cfg -python setup.py build_ext -i -pip install -e . +python setup.py develop popd popd diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/threadpoolctl-3.1.0/continuous_integration/posix.yml new/threadpoolctl-3.2.0/continuous_integration/posix.yml --- old/threadpoolctl-3.1.0/continuous_integration/posix.yml 2021-09-24 10:58:15.330052900 +0200 +++ new/threadpoolctl-3.2.0/continuous_integration/posix.yml 2023-07-13 16:19:36.077411700 +0200 @@ -21,13 +21,6 @@ displayName: Take ownership of conda installation condition: eq('${{ parameters.name }}', 'macOS') - script: | - conda create -n tmp -y -c conda-forge python black - source activate tmp - black --check . - conda deactivate - displayName: Lint - condition: eq(variables['LINT'], 'true') - - script: | continuous_integration/install.sh displayName: 'Install without BLIS' condition: ne(variables['INSTALL_BLIS'], 'true') diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/threadpoolctl-3.1.0/multiple_openmp.md new/threadpoolctl-3.2.0/multiple_openmp.md --- old/threadpoolctl-3.1.0/multiple_openmp.md 2021-09-15 15:26:21.493843300 +0200 +++ new/threadpoolctl-3.2.0/multiple_openmp.md 2023-07-12 10:17:49.739681000 +0200 @@ -29,13 +29,13 @@ `libgomp` and `libiomp`, which is the most common mix (NumPy with MKL + a package compiled with GCC, the most widely used C compiler on that platform). -## Incompatibility between Intel OpenMP and LLVM OpenMP under Linux +## Incompatibility between Intel OpenMP and LLVM OpenMP The only unrecoverable incompatibility we encountered happens when loading a mix of compiled extensions linked with **`libomp` (LLVM/Clang) and `libiomp` -(ICC), on Linux**, manifested by crashes or deadlocks. It can happen even with -the simplest OpenMP calls like getting the maximum number of threads that will -be used in a subsequent parallel region. A possible explanation is that +(ICC), on Linux and macOS**, manifested by crashes or deadlocks. It can happen +even with the simplest OpenMP calls like getting the maximum number of threads +that will be used in a subsequent parallel region. A possible explanation is that `libomp` is actually a fork of `libiomp` causing name colliding for instance. Using `threadpoolctl` may crash your program in such a setting. @@ -43,21 +43,23 @@ binary distributions of Python packages for Linux use either GCC or ICC to build the Python scientific packages. Therefore this problem would only happen if some packagers decide to start shipping Python packages built with -LLVM/Clang instead of GCC. - -Surprisingly, we never encountered this kind of issue on macOS, where this mix -is the most frequent (Clang being the default C compiler on macOS). +LLVM/Clang instead of GCC (this is the case for instance with conda's default channel). ## Workarounds for Intel OpenMP and LLVM OpenMP case As far as we know, the only workaround consists in making sure only of one of the two incompatible OpenMP libraries is loaded. For example: -- Tell MKL (used by NumPy) to use the GNU OpenMP runtime instead of the Intel - OpenMP runtime by setting the following environment variable: +- Tell MKL (used by NumPy) to use another threading implementation instead of the Intel + OpenMP runtime. It can be the GNU OpenMP runtime on Linux or TBB on Linux and MacOS + for instance. This is done by setting the following environment variable: export MKL_THREADING_LAYER=GNU + or, if TBB is installed: + + export MKL_THREADING_LAYER=TBB + - Install a build of NumPy and SciPy linked against OpenBLAS instead of MKL. This can be done for instance by installing NumPy and SciPy from PyPI: @@ -82,4 +84,4 @@ reproducer written in C: - https://bugs.llvm.org/show_bug.cgi?id=43565 -- https://software.intel.com/en-us/forums/intel-c-compiler/topic/827607 +- https://community.intel.com/t5/Intel-C-Compiler/Cannot-call-OpenMP-functions-from-libiomp-after-calling-from/m-p/1176406 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/threadpoolctl-3.1.0/pyproject.toml new/threadpoolctl-3.2.0/pyproject.toml --- old/threadpoolctl-3.1.0/pyproject.toml 2022-01-31 16:08:24.277708800 +0100 +++ new/threadpoolctl-3.2.0/pyproject.toml 2023-07-12 10:17:49.743681400 +0200 @@ -8,20 +8,20 @@ author-email = "thomas.moreau.2...@gmail.com" home-page = "https://github.com/joblib/threadpoolctl" description-file = "README.md" -requires-python = ">=3.6" +requires-python = ">=3.8" license = "BSD-3-Clause" classifiers = [ "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", "Topic :: Software Development :: Libraries :: Python Modules", ] [tool.black] line-length = 88 -target_version = ['py36', 'py37', 'py38', 'py39'] -experimental_string_processing = true +target_version = ['py38', 'py39', 'py310', 'py311'] +preview = true diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/threadpoolctl-3.1.0/tests/_pyMylib/__init__.py new/threadpoolctl-3.2.0/tests/_pyMylib/__init__.py --- old/threadpoolctl-3.1.0/tests/_pyMylib/__init__.py 1970-01-01 01:00:00.000000000 +0100 +++ new/threadpoolctl-3.2.0/tests/_pyMylib/__init__.py 2023-07-12 10:17:49.743681400 +0200 @@ -0,0 +1,46 @@ +import ctypes +from pathlib import Path + +from threadpoolctl import LibController, register + + +path = Path(__file__).parent / "my_threaded_lib.so" +ctypes.CDLL(path) + + +class MyThreadedLibController(LibController): + # names for threadpoolctl's context filtering + user_api = "my_threaded_lib" + internal_api = "my_threaded_lib" + + # Patterns to identify the name of the linked library to load. + # If a dynamic library with a matching filename is linked to the python + # process, it will be loaded as the `dynlib` attribute of the LibController + # instance. + filename_prefixes = ("my_threaded_lib",) + + def get_num_threads(self): + # This function should return the current maximum number of threads, + # which is reported as "num_threads" by `ThreadpoolController.info`. + return getattr(self.dynlib, "mylib_get_num_threads")() + + def set_num_threads(self, num_threads): + # This function limits the maximum number of threads, + # when `ThreadpoolController.limit` is called. + getattr(self.dynlib, "mylib_set_num_threads")(num_threads) + + def get_version(self): + # This function returns the version of the linked library if it is exposed, + # which is reported as "version" by `ThreadpoolController.info`. + get_version = getattr(self.dynlib, "mylib_get_version") + get_version.restype = ctypes.c_char_p + return get_version().decode("utf-8") + + def set_additional_attributes(self): + # This function is called during the initialization of the LibController. + # Additional information meant to be exposed by `ThreadpoolController.info` + # should be set here as attributes of the LibController instance. + self.some_attr = "some_value" + + +register(MyThreadedLibController) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/threadpoolctl-3.1.0/tests/_pyMylib/my_threaded_lib.c new/threadpoolctl-3.2.0/tests/_pyMylib/my_threaded_lib.c --- old/threadpoolctl-3.1.0/tests/_pyMylib/my_threaded_lib.c 1970-01-01 01:00:00.000000000 +0100 +++ new/threadpoolctl-3.2.0/tests/_pyMylib/my_threaded_lib.c 2023-07-12 10:17:49.743681400 +0200 @@ -0,0 +1,17 @@ +int NUM_THREADS = 42; +int* NUM_THREADS_p = &NUM_THREADS; + + +int mylib_get_num_threads(){ + return *NUM_THREADS_p; +} + + +void mylib_set_num_threads(int num_threads){ + *NUM_THREADS_p = num_threads; +} + + +char* mylib_get_version(){ + return "2.0"; +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/threadpoolctl-3.1.0/tests/test_threadpoolctl.py new/threadpoolctl-3.2.0/tests/test_threadpoolctl.py --- old/threadpoolctl-3.1.0/tests/test_threadpoolctl.py 2022-01-31 16:08:24.277708800 +0100 +++ new/threadpoolctl-3.2.0/tests/test_threadpoolctl.py 2023-07-12 10:17:49.743681400 +0200 @@ -22,6 +22,21 @@ return lib_controller.internal_api == "openblas" and lib_controller.version is None +def skip_if_openblas_openmp(): + """Helper to skip tests with side effects when OpenBLAS has the OpenMP + threading layer. + """ + if any( + lib_controller.internal_api == "openblas" + and lib_controller.threading_layer == "openmp" + for lib_controller in ThreadpoolController().lib_controllers + ): + pytest.skip( + "Setting a limit on OpenBLAS when using the OpenMP threading layer also " + "impact the OpenMP library. They can't be controlled independently." + ) + + def effective_num_threads(nthreads, max_threads): if nthreads is None or nthreads > max_threads: return max_threads @@ -196,6 +211,10 @@ def test_threadpool_controller_limit(): # Check that using the limit method of ThreadpoolController only impact its # library controllers. + + # This is not True for OpenBLAS with the OpenMP threading layer. + skip_if_openblas_openmp() + blas_controller = ThreadpoolController().select(user_api="blas") original_openmp_info = ThreadpoolController().select(user_api="openmp").info() @@ -208,14 +227,8 @@ for lib_controller in blas_controller.lib_controllers ) # original_blas_controller contains only blas libraries so no opemp library - # should be impacted. This is not True for OpenBLAS with the OpenMP threading - # layer. - if not any( - lib_controller.internal_api == "openblas" - and lib_controller.threading_layer == "openmp" - for lib_controller in blas_controller.lib_controllers - ): - assert openmp_info == original_openmp_info + # should be impacted. + assert openmp_info == original_openmp_info def test_get_params_for_sequential_blas_under_openmp(): @@ -407,6 +420,8 @@ check_nested_prange_blas = prange_blas.check_nested_prange_blas + skip_if_openblas_openmp() + original_info = ThreadpoolController().info() blas_controller = ThreadpoolController().select(user_api="blas") @@ -445,7 +460,7 @@ # the method `get_original_num_threads` raises a UserWarning due to different # num_threads from libraries with the same `user_api`. It will be raised only -# in the CI job with 2 openblas (py37_pip_openblas_gcc_clang). It is expected +# in the CI job with 2 openblas (py38_pip_openblas_gcc_clang). It is expected # so we can safely filter it. @pytest.mark.filterwarnings("ignore::UserWarning") @pytest.mark.parametrize("limit", [1, None]) @@ -657,3 +672,33 @@ outer_func() assert ThreadpoolController().info() == original_info + + +def test_custom_controller(): + # Check that a custom controller can be used to change the number of threads + # used by a library. + try: + import tests._pyMylib # noqa + except: + pytest.skip("requires my_thread_lib to be compiled") + + controller = ThreadpoolController() + original_info = controller.info() + + mylib_controller = controller.select(user_api="my_threaded_lib") + + # my_threaded_lib has been found and there's 1 matching shared library + assert len(mylib_controller.lib_controllers) == 1 + mylib_controller = mylib_controller.lib_controllers[0] + + # we linked against my_threaded_lib v2.0 and by default it uses 42 thread + assert mylib_controller.version == "2.0" + assert mylib_controller.num_threads == 42 + + # my_threaded_lib exposes an additional info "some_attr": + assert mylib_controller.info()["some_attr"] == "some_value" + + with controller.limit(limits=1, user_api="my_threaded_lib"): + assert mylib_controller.num_threads == 1 + + assert ThreadpoolController().info() == original_info diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/threadpoolctl-3.1.0/threadpoolctl.py new/threadpoolctl-3.2.0/threadpoolctl.py --- old/threadpoolctl-3.1.0/threadpoolctl.py 2022-01-31 17:25:14.640525300 +0100 +++ new/threadpoolctl-3.2.0/threadpoolctl.py 2023-07-13 16:44:13.629080800 +0200 @@ -15,14 +15,21 @@ import sys import ctypes import textwrap +from typing import final import warnings from ctypes.util import find_library from abc import ABC, abstractmethod from functools import lru_cache from contextlib import ContextDecorator -__version__ = "3.1.0" -__all__ = ["threadpool_limits", "threadpool_info", "ThreadpoolController"] +__version__ = "3.2.0" +__all__ = [ + "threadpool_limits", + "threadpool_info", + "ThreadpoolController", + "LibController", + "register", +] # One can get runtime errors or even segfaults due to multiple OpenMP libraries @@ -40,8 +47,8 @@ # Structure to cast the info on dynamically loaded library. See # https://linux.die.net/man/3/dl_iterate_phdr for more details. -_SYSTEM_UINT = ctypes.c_uint64 if sys.maxsize > 2 ** 32 else ctypes.c_uint32 -_SYSTEM_UINT_HALF = ctypes.c_uint32 if sys.maxsize > 2 ** 32 else ctypes.c_uint16 +_SYSTEM_UINT = ctypes.c_uint64 if sys.maxsize > 2**32 else ctypes.c_uint32 +_SYSTEM_UINT_HALF = ctypes.c_uint32 if sys.maxsize > 2**32 else ctypes.c_uint16 class _dl_phdr_info(ctypes.Structure): @@ -60,56 +67,318 @@ _RTLD_NOLOAD = ctypes.DEFAULT_MODE -# List of the supported libraries. The items are indexed by the name of the -# class to instantiate to create the library controller objects. The items hold -# the possible prefixes of loaded shared objects, the name of the internal_api -# to call, the name of the user_api and potentially some symbols that the library is -# expected to have (this is necessary to distinguish between the blas implementations -# when they are all renamed "libblas.dll" on conda-forge on windows). -_SUPPORTED_LIBRARIES = { - "OpenMPController": { - "user_api": "openmp", - "internal_api": "openmp", - "filename_prefixes": ("libiomp", "libgomp", "libomp", "vcomp"), - }, - "OpenBLASController": { - "user_api": "blas", - "internal_api": "openblas", - "filename_prefixes": ("libopenblas", "libblas"), - "check_symbols": ("openblas_get_num_threads", "openblas_get_num_threads64_"), - }, - "MKLController": { - "user_api": "blas", - "internal_api": "mkl", - "filename_prefixes": ("libmkl_rt", "mkl_rt", "libblas"), - "check_symbols": ("MKL_Get_Max_Threads",), - }, - "BLISController": { - "user_api": "blas", - "internal_api": "blis", - "filename_prefixes": ("libblis", "libblas"), - "check_symbols": ("bli_thread_get_num_threads",), - }, -} +class LibController(ABC): + """Abstract base class for the individual library controllers + + A library controller must expose the following class attributes: + - user_api : str + Usually the name of the library or generic specification the library + implements, e.g. "blas" is a specification with different implementations. + - internal_api : str + Usually the name of the library or concrete implementation of some + specification, e.g. "openblas" is an implementation of the "blas" + specification. + - filename_prefixes : tuple + Possible prefixes of the shared library's filename that allow to + identify the library. e.g. "libopenblas" for libopenblas.so. + + and implement the following methods: `get_num_threads`, `set_num_threads` and + `get_version`. + + Threadpoolctl loops through all the loaded shared libraries and tries to match + the filename of each library with the `filename_prefixes`. If a match is found, a + controller is instantiated and a handler to the library is stored in the `dynlib` + attribute as a `ctypes.CDLL` object. It can be used to access the necessary symbols + of the shared library to implement the above methods. + + The following information will be exposed in the info dictionary: + - user_api : standardized API, if any, or a copy of internal_api. + - internal_api : implementation-specific API. + - num_threads : the current thread limit. + - prefix : prefix of the shared library's filename. + - filepath : path to the loaded shared library. + - version : version of the library (if available). + + In addition, each library controller may expose internal API specific entries. They + must be set as attributes in the `set_additional_attributes` method. + """ + + @final + def __init__(self, *, filepath=None, prefix=None): + """This is not meant to be overriden by subclasses.""" + self.prefix = prefix + self.filepath = filepath + self.dynlib = ctypes.CDLL(filepath, mode=_RTLD_NOLOAD) + self.version = self.get_version() + self.set_additional_attributes() + + @final + def info(self): + """Return relevant info wrapped in a dict + + This is not meant to be overriden by subclasses. + """ + exposed_attrs = { + "user_api": self.user_api, + "internal_api": self.internal_api, + "num_threads": self.num_threads, + **vars(self), + } + exposed_attrs.pop("dynlib") + return exposed_attrs + + def set_additional_attributes(self): + """Set additional attributes meant to be exposed in the info dict""" + + @property + def num_threads(self): + """Exposes the current thread limit as a dynamic property + + This is not meant to be used or overriden by subclasses. + """ + return self.get_num_threads() + + @abstractmethod + def get_num_threads(self): + """Return the maximum number of threads available to use""" + + @abstractmethod + def set_num_threads(self, num_threads): + """Set the maximum number of threads to use""" + + @abstractmethod + def get_version(self): + """Return the version of the shared library""" + + +class OpenBLASController(LibController): + """Controller class for OpenBLAS""" + + user_api = "blas" + internal_api = "openblas" + filename_prefixes = ("libopenblas", "libblas") + check_symbols = ("openblas_get_num_threads", "openblas_get_num_threads64_") + + def set_additional_attributes(self): + self.threading_layer = self._get_threading_layer() + self.architecture = self._get_architecture() + + def get_num_threads(self): + get_func = getattr( + self.dynlib, + "openblas_get_num_threads", + # Symbols differ when built for 64bit integers in Fortran + getattr(self.dynlib, "openblas_get_num_threads64_", lambda: None), + ) + + return get_func() + + def set_num_threads(self, num_threads): + set_func = getattr( + self.dynlib, + "openblas_set_num_threads", + # Symbols differ when built for 64bit integers in Fortran + getattr( + self.dynlib, "openblas_set_num_threads64_", lambda num_threads: None + ), + ) + return set_func(num_threads) + + def get_version(self): + # None means OpenBLAS is not loaded or version < 0.3.4, since OpenBLAS + # did not expose its version before that. + get_config = getattr( + self.dynlib, + "openblas_get_config", + getattr(self.dynlib, "openblas_get_config64_", None), + ) + if get_config is None: + return None + + get_config.restype = ctypes.c_char_p + config = get_config().split() + if config[0] == b"OpenBLAS": + return config[1].decode("utf-8") + return None + + def _get_threading_layer(self): + """Return the threading layer of OpenBLAS""" + openblas_get_parallel = getattr( + self.dynlib, + "openblas_get_parallel", + getattr(self.dynlib, "openblas_get_parallel64_", None), + ) + if openblas_get_parallel is None: + return "unknown" + threading_layer = openblas_get_parallel() + if threading_layer == 2: + return "openmp" + elif threading_layer == 1: + return "pthreads" + return "disabled" + + def _get_architecture(self): + """Return the architecture detected by OpenBLAS""" + get_corename = getattr( + self.dynlib, + "openblas_get_corename", + getattr(self.dynlib, "openblas_get_corename64_", None), + ) + if get_corename is None: + return None + + get_corename.restype = ctypes.c_char_p + return get_corename().decode("utf-8") + + +class BLISController(LibController): + """Controller class for BLIS""" + + user_api = "blas" + internal_api = "blis" + filename_prefixes = ("libblis", "libblas") + check_symbols = ("bli_thread_get_num_threads",) + + def set_additional_attributes(self): + self.threading_layer = self._get_threading_layer() + self.architecture = self._get_architecture() + + def get_num_threads(self): + get_func = getattr(self.dynlib, "bli_thread_get_num_threads", lambda: None) + num_threads = get_func() + # by default BLIS is single-threaded and get_num_threads + # returns -1. We map it to 1 for consistency with other libraries. + return 1 if num_threads == -1 else num_threads + + def set_num_threads(self, num_threads): + set_func = getattr( + self.dynlib, "bli_thread_set_num_threads", lambda num_threads: None + ) + return set_func(num_threads) + + def get_version(self): + get_version_ = getattr(self.dynlib, "bli_info_get_version_str", None) + if get_version_ is None: + return None + + get_version_.restype = ctypes.c_char_p + return get_version_().decode("utf-8") + + def _get_threading_layer(self): + """Return the threading layer of BLIS""" + if self.dynlib.bli_info_get_enable_openmp(): + return "openmp" + elif self.dynlib.bli_info_get_enable_pthreads(): + return "pthreads" + return "disabled" + + def _get_architecture(self): + """Return the architecture detected by BLIS""" + bli_arch_query_id = getattr(self.dynlib, "bli_arch_query_id", None) + bli_arch_string = getattr(self.dynlib, "bli_arch_string", None) + if bli_arch_query_id is None or bli_arch_string is None: + return None + + # the true restype should be BLIS' arch_t (enum) but int should work + # for us: + bli_arch_query_id.restype = ctypes.c_int + bli_arch_string.restype = ctypes.c_char_p + return bli_arch_string(bli_arch_query_id()).decode("utf-8") + + +class MKLController(LibController): + """Controller class for MKL""" + + user_api = "blas" + internal_api = "mkl" + filename_prefixes = ("libmkl_rt", "mkl_rt", "libblas") + check_symbols = ("MKL_Get_Max_Threads",) + + def set_additional_attributes(self): + self.threading_layer = self._get_threading_layer() + + def get_num_threads(self): + get_func = getattr(self.dynlib, "MKL_Get_Max_Threads", lambda: None) + return get_func() + + def set_num_threads(self, num_threads): + set_func = getattr(self.dynlib, "MKL_Set_Num_Threads", lambda num_threads: None) + return set_func(num_threads) + + def get_version(self): + if not hasattr(self.dynlib, "MKL_Get_Version_String"): + return None + + res = ctypes.create_string_buffer(200) + self.dynlib.MKL_Get_Version_String(res, 200) + + version = res.value.decode("utf-8") + group = re.search(r"Version ([^ ]+) ", version) + if group is not None: + version = group.groups()[0] + return version.strip() + + def _get_threading_layer(self): + """Return the threading layer of MKL""" + # The function mkl_set_threading_layer returns the current threading + # layer. Calling it with an invalid threading layer allows us to safely + # get the threading layer + set_threading_layer = getattr( + self.dynlib, "MKL_Set_Threading_Layer", lambda layer: -1 + ) + layer_map = { + 0: "intel", + 1: "sequential", + 2: "pgi", + 3: "gnu", + 4: "tbb", + -1: "not specified", + } + return layer_map[set_threading_layer(-1)] + + +class OpenMPController(LibController): + """Controller class for OpenMP""" + + user_api = "openmp" + internal_api = "openmp" + filename_prefixes = ("libiomp", "libgomp", "libomp", "vcomp") + + def get_num_threads(self): + get_func = getattr(self.dynlib, "omp_get_max_threads", lambda: None) + return get_func() + + def set_num_threads(self, num_threads): + set_func = getattr(self.dynlib, "omp_set_num_threads", lambda num_threads: None) + return set_func(num_threads) + + def get_version(self): + # There is no way to get the version number programmatically in OpenMP. + return None + + +# Controllers for the libraries that we'll look for in the loaded libraries. +# Third party libraries can register their own controllers. +_ALL_CONTROLLERS = [OpenBLASController, BLISController, MKLController, OpenMPController] # Helpers for the doc and test names -_ALL_USER_APIS = list(set(lib["user_api"] for lib in _SUPPORTED_LIBRARIES.values())) -_ALL_INTERNAL_APIS = [lib["internal_api"] for lib in _SUPPORTED_LIBRARIES.values()] +_ALL_USER_APIS = list(set(lib.user_api for lib in _ALL_CONTROLLERS)) +_ALL_INTERNAL_APIS = [lib.internal_api for lib in _ALL_CONTROLLERS] _ALL_PREFIXES = list( - set( - prefix - for lib in _SUPPORTED_LIBRARIES.values() - for prefix in lib["filename_prefixes"] - ) + set(prefix for lib in _ALL_CONTROLLERS for prefix in lib.filename_prefixes) ) _ALL_BLAS_LIBRARIES = [ - lib["internal_api"] - for lib in _SUPPORTED_LIBRARIES.values() - if lib["user_api"] == "blas" + lib.internal_api for lib in _ALL_CONTROLLERS if lib.user_api == "blas" ] -_ALL_OPENMP_LIBRARIES = list( - _SUPPORTED_LIBRARIES["OpenMPController"]["filename_prefixes"] -) +_ALL_OPENMP_LIBRARIES = OpenMPController.filename_prefixes + + +def register(controller): + """Register a new controller""" + _ALL_CONTROLLERS.append(controller) + _ALL_USER_APIS.append(controller.user_api) + _ALL_INTERNAL_APIS.append(controller.internal_api) + _ALL_PREFIXES.extend(controller.filename_prefixes) def _format_docstring(*args, **kwargs): @@ -377,12 +646,6 @@ return super().wrap(ThreadpoolController(), limits=limits, user_api=user_api) -@_format_docstring( - PREFIXES=", ".join(f'"{prefix}"' for prefix in _ALL_PREFIXES), - USER_APIS=", ".join(f'"{api}"' for api in _ALL_USER_APIS), - BLAS_LIBS=", ".join(_ALL_BLAS_LIBRARIES), - OPENMP_LIBS=", ".join(_ALL_OPENMP_LIBRARIES), -) class ThreadpoolController: """Collection of LibController objects for all loaded supported libraries @@ -664,7 +927,6 @@ buf = ctypes.create_unicode_buffer(MAX_PATH) n_size = DWORD() for h_module in h_modules: - # Get the path of the current module if not ps_api.GetModuleFileNameExW( h_process, h_module, ctypes.byref(buf), ctypes.byref(n_size) @@ -687,9 +949,9 @@ # Loop through supported libraries to find if this filename corresponds # to a supported one. - for controller_class, candidate_lib in _SUPPORTED_LIBRARIES.items(): + for controller_class in _ALL_CONTROLLERS: # check if filename matches a supported prefix - prefix = self._check_prefix(filename, candidate_lib["filename_prefixes"]) + prefix = self._check_prefix(filename, controller_class.filename_prefixes) # filename does not match any of the prefixes of the candidate # library. move to next library. @@ -705,7 +967,7 @@ libblas = ctypes.CDLL(filepath, _RTLD_NOLOAD) if not any( hasattr(libblas, func) - for func in candidate_lib["check_symbols"] + for func in controller_class.check_symbols ): continue else: @@ -718,16 +980,8 @@ # filename matches a prefix. Create and store the library # controller. - user_api = candidate_lib["user_api"] - internal_api = candidate_lib["internal_api"] - lib_controller_class = globals()[controller_class] - lib_controller = lib_controller_class( - filepath=filepath, - prefix=prefix, - user_api=user_api, - internal_api=internal_api, - ) + lib_controller = controller_class(filepath=filepath, prefix=prefix) self.lib_controllers.append(lib_controller) def _check_prefix(self, library_basename, filename_prefixes): @@ -742,13 +996,8 @@ def _warn_if_incompatible_openmp(self): """Raise a warning if llvm-OpenMP and intel-OpenMP are both loaded""" - if sys.platform != "linux": - # Only raise the warning on linux - return - prefixes = [lib_controller.prefix for lib_controller in self.lib_controllers] - msg = textwrap.dedent( - """ + msg = textwrap.dedent(""" Found Intel OpenMP ('libiomp') and LLVM OpenMP ('libomp') loaded at the same time. Both libraries are known to be incompatible and this can cause random crashes or deadlocks on Linux when loaded in the @@ -756,8 +1005,7 @@ Using threadpoolctl may cause crashes or deadlocks. For more information and possible workarounds, please see https://github.com/joblib/threadpoolctl/blob/master/multiple_openmp.md - """ - ) + """) if "libomp" in prefixes and "libiomp" in prefixes: warnings.warn(msg, RuntimeWarning) @@ -768,6 +1016,12 @@ if libc is None: libc_name = find_library("c") if libc_name is None: # pragma: no cover + warnings.warn( + "libc not found. The ctypes module in Python" + f" {sys.version_info.major}.{sys.version_info.minor} is maybe" + " too old for this OS.", + RuntimeWarning, + ) return None libc = ctypes.CDLL(libc_name, mode=_RTLD_NOLOAD) cls._system_libraries["libc"] = libc @@ -783,252 +1037,6 @@ return dll -@_format_docstring( - USER_APIS=", ".join('"{}"'.format(api) for api in _ALL_USER_APIS), - INTERNAL_APIS=", ".join('"{}"'.format(api) for api in _ALL_INTERNAL_APIS), -) -class LibController(ABC): - """Abstract base class for the individual library controllers - - A library controller is represented by the following information: - - "user_api" : user API. Possible values are {USER_APIS}. - - "internal_api" : internal API. Possible values are {INTERNAL_APIS}. - - "prefix" : prefix of the shared library's name. - - "filepath" : path to the loaded library. - - "version" : version of the library (if available). - - "num_threads" : the current thread limit. - - In addition, each library controller may contain internal_api specific - entries. - """ - - def __init__(self, *, filepath=None, prefix=None, user_api=None, internal_api=None): - self.user_api = user_api - self.internal_api = internal_api - self.prefix = prefix - self.filepath = filepath - self._dynlib = ctypes.CDLL(filepath, mode=_RTLD_NOLOAD) - self.version = self.get_version() - - def info(self): - """Return relevant info wrapped in a dict""" - all_attrs = dict(vars(self), **{"num_threads": self.num_threads}) - return {k: v for k, v in all_attrs.items() if not k.startswith("_")} - - @property - def num_threads(self): - return self.get_num_threads() - - @abstractmethod - def get_num_threads(self): - """Return the maximum number of threads available to use""" - pass # pragma: no cover - - @abstractmethod - def set_num_threads(self, num_threads): - """Set the maximum number of threads to use""" - pass # pragma: no cover - - @abstractmethod - def get_version(self): - """Return the version of the shared library""" - pass # pragma: no cover - - -class OpenBLASController(LibController): - """Controller class for OpenBLAS""" - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.threading_layer = self._get_threading_layer() - self.architecture = self._get_architecture() - - def get_num_threads(self): - get_func = getattr( - self._dynlib, - "openblas_get_num_threads", - # Symbols differ when built for 64bit integers in Fortran - getattr(self._dynlib, "openblas_get_num_threads64_", lambda: None), - ) - - return get_func() - - def set_num_threads(self, num_threads): - set_func = getattr( - self._dynlib, - "openblas_set_num_threads", - # Symbols differ when built for 64bit integers in Fortran - getattr( - self._dynlib, "openblas_set_num_threads64_", lambda num_threads: None - ), - ) - return set_func(num_threads) - - def get_version(self): - # None means OpenBLAS is not loaded or version < 0.3.4, since OpenBLAS - # did not expose its version before that. - get_config = getattr( - self._dynlib, - "openblas_get_config", - getattr(self._dynlib, "openblas_get_config64_", None), - ) - if get_config is None: - return None - - get_config.restype = ctypes.c_char_p - config = get_config().split() - if config[0] == b"OpenBLAS": - return config[1].decode("utf-8") - return None - - def _get_threading_layer(self): - """Return the threading layer of OpenBLAS""" - openblas_get_parallel = getattr( - self._dynlib, - "openblas_get_parallel", - getattr(self._dynlib, "openblas_get_parallel64_", None), - ) - if openblas_get_parallel is None: - return "unknown" - threading_layer = openblas_get_parallel() - if threading_layer == 2: - return "openmp" - elif threading_layer == 1: - return "pthreads" - return "disabled" - - def _get_architecture(self): - """Return the architecture detected by OpenBLAS""" - get_corename = getattr( - self._dynlib, - "openblas_get_corename", - getattr(self._dynlib, "openblas_get_corename64_", None), - ) - if get_corename is None: - return None - - get_corename.restype = ctypes.c_char_p - return get_corename().decode("utf-8") - - -class BLISController(LibController): - """Controller class for BLIS""" - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.threading_layer = self._get_threading_layer() - self.architecture = self._get_architecture() - - def get_num_threads(self): - get_func = getattr(self._dynlib, "bli_thread_get_num_threads", lambda: None) - num_threads = get_func() - # by default BLIS is single-threaded and get_num_threads - # returns -1. We map it to 1 for consistency with other libraries. - return 1 if num_threads == -1 else num_threads - - def set_num_threads(self, num_threads): - set_func = getattr( - self._dynlib, "bli_thread_set_num_threads", lambda num_threads: None - ) - return set_func(num_threads) - - def get_version(self): - get_version_ = getattr(self._dynlib, "bli_info_get_version_str", None) - if get_version_ is None: - return None - - get_version_.restype = ctypes.c_char_p - return get_version_().decode("utf-8") - - def _get_threading_layer(self): - """Return the threading layer of BLIS""" - if self._dynlib.bli_info_get_enable_openmp(): - return "openmp" - elif self._dynlib.bli_info_get_enable_pthreads(): - return "pthreads" - return "disabled" - - def _get_architecture(self): - """Return the architecture detected by BLIS""" - bli_arch_query_id = getattr(self._dynlib, "bli_arch_query_id", None) - bli_arch_string = getattr(self._dynlib, "bli_arch_string", None) - if bli_arch_query_id is None or bli_arch_string is None: - return None - - # the true restype should be BLIS' arch_t (enum) but int should work - # for us: - bli_arch_query_id.restype = ctypes.c_int - bli_arch_string.restype = ctypes.c_char_p - return bli_arch_string(bli_arch_query_id()).decode("utf-8") - - -class MKLController(LibController): - """Controller class for MKL""" - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.threading_layer = self._get_threading_layer() - - def get_num_threads(self): - get_func = getattr(self._dynlib, "MKL_Get_Max_Threads", lambda: None) - return get_func() - - def set_num_threads(self, num_threads): - set_func = getattr( - self._dynlib, "MKL_Set_Num_Threads", lambda num_threads: None - ) - return set_func(num_threads) - - def get_version(self): - if not hasattr(self._dynlib, "MKL_Get_Version_String"): - return None - - res = ctypes.create_string_buffer(200) - self._dynlib.MKL_Get_Version_String(res, 200) - - version = res.value.decode("utf-8") - group = re.search(r"Version ([^ ]+) ", version) - if group is not None: - version = group.groups()[0] - return version.strip() - - def _get_threading_layer(self): - """Return the threading layer of MKL""" - # The function mkl_set_threading_layer returns the current threading - # layer. Calling it with an invalid threading layer allows us to safely - # get the threading layer - set_threading_layer = getattr( - self._dynlib, "MKL_Set_Threading_Layer", lambda layer: -1 - ) - layer_map = { - 0: "intel", - 1: "sequential", - 2: "pgi", - 3: "gnu", - 4: "tbb", - -1: "not specified", - } - return layer_map[set_threading_layer(-1)] - - -class OpenMPController(LibController): - """Controller class for OpenMP""" - - def get_num_threads(self): - get_func = getattr(self._dynlib, "omp_get_max_threads", lambda: None) - return get_func() - - def set_num_threads(self, num_threads): - set_func = getattr( - self._dynlib, "omp_set_num_threads", lambda num_threads: None - ) - return set_func(num_threads) - - def get_version(self): - # There is no way to get the version number programmatically in OpenMP. - return None - - def _main(): """Commandline interface to display thread-pool information and exit.""" import argparse