]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-109649: Use os.process_cpu_count() (#110165)
authorVictor Stinner <vstinner@python.org>
Sun, 1 Oct 2023 01:14:57 +0000 (03:14 +0200)
committerGitHub <noreply@github.com>
Sun, 1 Oct 2023 01:14:57 +0000 (03:14 +0200)
Replace os.cpu_count() with os.process_cpu_count() in modules:

* compileall
* concurrent.futures
* multiprocessing

Replace os.cpu_count() with os.process_cpu_count() in programs:

* _decimal deccheck.py test
* freeze.py
* multissltests.py
* python -m test (regrtest)
* wasm_build.py

Other changes:

* test.pythoninfo logs os.process_cpu_count().
* regrtest gets os.process_cpu_count() / os.cpu_count() in headers.

16 files changed:
Doc/library/compileall.rst
Doc/library/concurrent.futures.rst
Doc/library/multiprocessing.rst
Doc/whatsnew/3.13.rst
Lib/concurrent/futures/process.py
Lib/concurrent/futures/thread.py
Lib/multiprocessing/pool.py
Lib/test/libregrtest/main.py
Lib/test/libregrtest/utils.py
Lib/test/pythoninfo.py
Lib/test/test_concurrent_futures/test_thread_pool.py
Misc/NEWS.d/next/Library/2023-10-01-01-47-21.gh-issue-109649.BizOaD.rst [new file with mode: 0644]
Modules/_decimal/tests/deccheck.py
Tools/freeze/test/freeze.py
Tools/ssl/multissltests.py
Tools/wasm/wasm_build.py

index a7455aeb0ec1cd6e6e8e77e254e38a1765d3b303..b4723b98f67bb228499a985c1c5fdacc5384bb9d 100644 (file)
@@ -90,7 +90,7 @@ compile Python sources.
 .. cmdoption:: -j N
 
    Use *N* workers to compile the files within the given directory.
-   If ``0`` is used, then the result of :func:`os.cpu_count()`
+   If ``0`` is used, then the result of :func:`os.process_cpu_count()`
    will be used.
 
 .. cmdoption:: --invalidation-mode [timestamp|checked-hash|unchecked-hash]
index 6503d1fcf70a32554904302aaedd2c1447e424d2..dca51459a2df9891b0c66f440f52c77c77e23b76 100644 (file)
@@ -188,6 +188,10 @@ And::
       ThreadPoolExecutor now reuses idle worker threads before starting
       *max_workers* worker threads too.
 
+   .. versionchanged:: 3.13
+      Default value of *max_workers* is changed to
+      ``min(32, (os.process_cpu_count() or 1) + 4)``.
+
 
 .. _threadpoolexecutor-example:
 
@@ -243,7 +247,7 @@ to a :class:`ProcessPoolExecutor` will result in deadlock.
 
    An :class:`Executor` subclass that executes calls asynchronously using a pool
    of at most *max_workers* processes.  If *max_workers* is ``None`` or not
-   given, it will default to the number of processors on the machine.
+   given, it will default to :func:`os.process_cpu_count`.
    If *max_workers* is less than or equal to ``0``, then a :exc:`ValueError`
    will be raised.
    On Windows, *max_workers* must be less than or equal to ``61``. If it is not
@@ -301,6 +305,10 @@ to a :class:`ProcessPoolExecutor` will result in deadlock.
       different start method. See the :func:`os.fork` documentation for
       further explanation.
 
+   .. versionchanged:: 3.13
+      *max_workers* uses :func:`os.process_cpu_count` by default, instead of
+      :func:`os.cpu_count`.
+
 .. _processpoolexecutor-example:
 
 ProcessPoolExecutor Example
index 2f0f1f800fdc944c1f57d3d3d3b317457d7a49a4..d19f911dd7016cdba958329f2ffe409c761d6aa2 100644 (file)
@@ -996,13 +996,13 @@ Miscellaneous
 
    This number is not equivalent to the number of CPUs the current process can
    use.  The number of usable CPUs can be obtained with
-   ``len(os.sched_getaffinity(0))``
+   :func:`os.process_cpu_count`.
 
    When the number of CPUs cannot be determined a :exc:`NotImplementedError`
    is raised.
 
    .. seealso::
-      :func:`os.cpu_count`
+      :func:`os.cpu_count` and :func:`os.process_cpu_count`
 
 .. function:: current_process()
 
@@ -2214,7 +2214,7 @@ with the :class:`Pool` class.
    callbacks and has a parallel map implementation.
 
    *processes* is the number of worker processes to use.  If *processes* is
-   ``None`` then the number returned by :func:`os.cpu_count` is used.
+   ``None`` then the number returned by :func:`os.process_cpu_count` is used.
 
    If *initializer* is not ``None`` then each worker process will call
    ``initializer(*initargs)`` when it starts.
@@ -2249,6 +2249,10 @@ with the :class:`Pool` class.
    .. versionadded:: 3.4
       *context*
 
+   .. versionchanged:: 3.13
+      *processes* uses :func:`os.process_cpu_count` by default, instead of
+      :func:`os.cpu_count`.
+
    .. note::
 
       Worker processes within a :class:`Pool` typically live for the complete
@@ -2775,7 +2779,7 @@ worker threads rather than worker processes.
    :meth:`~multiprocessing.pool.Pool.terminate` manually.
 
    *processes* is the number of worker threads to use.  If *processes* is
-   ``None`` then the number returned by :func:`os.cpu_count` is used.
+   ``None`` then the number returned by :func:`os.process_cpu_count` is used.
 
    If *initializer* is not ``None`` then each worker process will call
    ``initializer(*initargs)`` when it starts.
index 484443a086fdd6b9f4441a680741479e2050cd16..a789084a79c397e5154533f3a00fdff1b231a2f1 100644 (file)
@@ -91,6 +91,13 @@ Other Language Changes
   of the ``optimize`` argument.
   (Contributed by Irit Katriel in :gh:`108113`).
 
+* :mod:`multiprocessing`, :mod:`concurrent.futures`, :mod:`compileall`:
+  Replace :func:`os.cpu_count` with :func:`os.process_cpu_count` to select the
+  default number of worker threads and processes. Get the CPU affinity
+  if supported.
+  (Contributed by Victor Stinner in :gh:`109649`.)
+
+
 New Modules
 ===========
 
index 3990e6b1833d7860888cea13ea27cd98b5150d62..ffaffdb8b3d0aad67928d485b9958ad7d1f841cb 100644 (file)
@@ -666,7 +666,7 @@ class ProcessPoolExecutor(_base.Executor):
         _check_system_limits()
 
         if max_workers is None:
-            self._max_workers = os.cpu_count() or 1
+            self._max_workers = os.process_cpu_count() or 1
             if sys.platform == 'win32':
                 self._max_workers = min(_MAX_WINDOWS_WORKERS,
                                         self._max_workers)
index 3b3a36a5093336cfd9491d3cf269d49fdac9dd63..a024033f35fb548f07d3a2a849834a8d6a781368 100644 (file)
@@ -139,10 +139,10 @@ class ThreadPoolExecutor(_base.Executor):
             # * CPU bound task which releases GIL
             # * I/O bound task (which releases GIL, of course)
             #
-            # We use cpu_count + 4 for both types of tasks.
+            # We use process_cpu_count + 4 for both types of tasks.
             # But we limit it to 32 to avoid consuming surprisingly large resource
             # on many core machine.
-            max_workers = min(32, (os.cpu_count() or 1) + 4)
+            max_workers = min(32, (os.process_cpu_count() or 1) + 4)
         if max_workers <= 0:
             raise ValueError("max_workers must be greater than 0")
 
index 4f5d88cb975cb75aa538db4d526b7e7f88e040f5..f979890170b1a1f8721f0826169a440ae0859749 100644 (file)
@@ -200,7 +200,7 @@ class Pool(object):
         self._initargs = initargs
 
         if processes is None:
-            processes = os.cpu_count() or 1
+            processes = os.process_cpu_count() or 1
         if processes < 1:
             raise ValueError("Number of processes must be at least 1")
         if maxtasksperchild is not None:
index 19bf2358456036fc67fbc4789f73ff5aa8199e32..5f2baac9cba9e014a1086fb15888794d9ffcf534 100644 (file)
@@ -426,7 +426,7 @@ class Regrtest:
         if self.num_workers < 0:
             # Use all CPUs + 2 extra worker processes for tests
             # that like to sleep
-            self.num_workers = (os.cpu_count() or 1) + 2
+            self.num_workers = (os.process_cpu_count() or 1) + 2
 
         # For a partial run, we do not need to clutter the output.
         if (self.want_header
index d2c274d9970738a531a66784b12690fe82eec0fb..86fb820a23f535c24d6433a8f48265c37fb58981 100644 (file)
@@ -546,6 +546,9 @@ def display_header(use_resources: tuple[str, ...],
 
     cpu_count = os.cpu_count()
     if cpu_count:
+        process_cpu_count = os.process_cpu_count()
+        if process_cpu_count and process_cpu_count != cpu_count:
+            cpu_count = f"{process_cpu_count} (process) / {cpu_count} (system)"
         print("== CPU count:", cpu_count)
     print("== encodings: locale=%s, FS=%s"
           % (locale.getencoding(), sys.getfilesystemencoding()))
index 0e7528ef97c5f68c7b58eb5f761b4229b206143c..58d906ffc62a53375d51af9b801b11c4710e2b6e 100644 (file)
@@ -239,6 +239,7 @@ def collect_os(info_add):
         'getresgid',
         'getresuid',
         'getuid',
+        'process_cpu_count',
         'uname',
     ):
         call_func(info_add, 'os.%s' % func, os, func)
index 812f989d8f3ad2deae2bbd107f9c95acc17dd602..5926a632aa4bec23dbf6a654b127a692f707c854 100644 (file)
@@ -25,7 +25,7 @@ class ThreadPoolExecutorTest(ThreadPoolMixin, ExecutorTest, BaseTestCase):
 
     def test_default_workers(self):
         executor = self.executor_type()
-        expected = min(32, (os.cpu_count() or 1) + 4)
+        expected = min(32, (os.process_cpu_count() or 1) + 4)
         self.assertEqual(executor._max_workers, expected)
 
     def test_saturation(self):
diff --git a/Misc/NEWS.d/next/Library/2023-10-01-01-47-21.gh-issue-109649.BizOaD.rst b/Misc/NEWS.d/next/Library/2023-10-01-01-47-21.gh-issue-109649.BizOaD.rst
new file mode 100644 (file)
index 0000000..888fd79
--- /dev/null
@@ -0,0 +1,4 @@
+:mod:`multiprocessing`, :mod:`concurrent.futures`, :mod:`compileall`:
+Replace :func:`os.cpu_count` with :func:`os.process_cpu_count` to select the
+default number of worker threads and processes. Get the CPU affinity if
+supported. Patch by Victor Stinner.
index edf753f3704a18347ac3df8a4de7ee7368e46529..bf277dd6879ffe7317e0b5ca1b2e56c307c9c1a4 100644 (file)
@@ -1301,7 +1301,7 @@ if __name__ == '__main__':
                 out, _ = p.communicate()
                 write_output(out, p.returncode)
 
-        N = os.cpu_count()
+        N = os.process_cpu_count()
         t = N * [None]
 
         for i in range(N):
index cdf77c57bbb6aeba289a27f6f43a706f462a50b0..9030ad4d4e5f93d5b559d19537a64757db88b846 100644 (file)
@@ -130,7 +130,7 @@ def prepare(script=None, outdir=None):
     if not MAKE:
         raise UnsupportedError('make')
 
-    cores = os.cpu_count()
+    cores = os.process_cpu_count()
     if cores and cores >= 3:
         # this test is most often run as part of the whole suite with a lot
         # of other tests running in parallel, from 1-2 vCPU systems up to
index f066fb52cfd49671ef541db434b29fb2bf4e9457..120e3883adc795ddaf6e52aaa6bf1d0c00fd2df4 100755 (executable)
@@ -151,7 +151,10 @@ class AbstractBuilder(object):
     build_template = None
     depend_target = None
     install_target = 'install'
-    jobs = os.cpu_count()
+    if hasattr(os, 'process_cpu_count'):
+        jobs = os.process_cpu_count()
+    else:
+        jobs = os.cpu_count()
 
     module_files = (
         os.path.join(PYTHONROOT, "Modules/_ssl.c"),
index 3558ecd869dfc5815a58d2cbd0303b3802d1a0d3..c0b9999a5dad037165c3a3ae9323bebeb818d179 100755 (executable)
@@ -516,7 +516,11 @@ class BuildProfile:
     def getenv(self) -> Dict[str, Any]:
         """Generate environ dict for platform"""
         env = os.environ.copy()
-        env.setdefault("MAKEFLAGS", f"-j{os.cpu_count()}")
+        if hasattr(os, 'process_cpu_count'):
+            cpu_count = os.process_cpu_count()
+        else:
+            cpu_count = os.cpu_count()
+        env.setdefault("MAKEFLAGS", f"-j{cpu_count}")
         platenv = self.host.platform.getenv(self)
         for key, value in platenv.items():
             if value is None: