-
-
Notifications
You must be signed in to change notification settings - Fork 32k
gh-109649: Add os.process_cpu_count() function #109907
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
e3554fa
to
69c8bf4
Compare
Adding a new process_cpu_count() makes it easier to extend the function later for new use cases, instead of modifying the existing os.cpu_count():
About the pid parameter. First, I would like to implement this function on Windows (get process affinity), and then check if it would be possible to get the CPU affinity of another process on Windows, before considering to add a pid parameter. |
In psutil, this function is called Process.cpu_affinity(): https://psutil.readthedocs.io/en/latest/#psutil.Process.cpu_affinity |
69c8bf4
to
7832caa
Compare
I prefer adding a new
|
I will take a look at it by tomorrow! |
@gpshead: I updated my PR to reimplement os.process_cpu_count() in Python. |
cpu_set = sched_getaffinity(0)
num_cpus = cpu_count()
return min(len(cpu_set), num_cpus) if cpu_set else num_cpus Why do you want to return cpu_count() if it's smaller than len(sched_getaffinity(0))? It should not happen. If it happens, I would prefer to not workaround bugs, but fix the OS instead. For me, it should not happen.
When I added I recall that Python detects broken poll() implementation on macOS. In that case, we go further: we remove the function at runtime! (at Python startup) I prefer to not make assumptions about hypothetical bugs, but wait until we get real concrete bug reports, and then decide how to deal with them. Anyway, thanks for thinking about all corner cases, it's a good thing! |
If we pass the -Xcpu_count= |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall LGTM, for the detail, I will delegate it to @gpshead
I would prefer that the discussion separately the implementation and the expected behavior. Also, can we discussion -Xcpu_count later? This PR is about adding process_cpu_count(), nothing more. |
Doc/library/os.rst
Outdated
@@ -5202,6 +5200,17 @@ Miscellaneous System Information | |||
.. availability:: Unix. | |||
|
|||
|
|||
.. function:: process_cpu_count() | |||
|
|||
Get the number of logical CPUs usable by the current process. Returns |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is probably more accurate to say "usable by the calling thread of the current process". I believe each thread can have its own affinity.
(For parallelism planning purposes, the main thread prior to spawning others is likely what people would be calling this from anyways)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sadly, you're right. I updated the doc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One documentation improvement suggested but otherwise good.
@gpshead is right, the number of CPUs is "per thread" in thread. Example: import os
import threading
def sched_remove_one_cpu():
mask = os.sched_getaffinity(0)
mask.pop()
os.sched_setaffinity(0, mask)
def set_affinity():
tid = threading.get_native_id()
print(f"thread {tid}: remove a second CPU")
sched_remove_one_cpu()
print(f"thread {tid}: {os.process_cpu_count()=}")
tid = threading.get_native_id()
print(f"main thread {tid}: remove a CPU")
sched_remove_one_cpu()
print(f"main thread {tid} before: {os.cpu_count()=}")
print(f"main thread {tid} before: {os.process_cpu_count()=}")
print()
thread = threading.Thread(target=set_affinity)
thread.start()
thread.join()
print()
print(f"main thread {tid} after: {os.cpu_count()=}")
print(f"main thread {tid} after: {os.process_cpu_count()=}") Output on Linux:
You can see that the main thread loses a CPU when sched_remove_one_cpu() is called: os.process_cpu_count() is affected, but os.cpu_count() is not affected. When a thread removes a second CPU, it affects os.process_cpu_count() in the thread, but the second CPU removal does not affected the main thread. New thread inherit the mask of the caller thread, but a change in a child thread does not affect the parent thread. Well, that's the expected behavior. |
* Refactor os_sched_getaffinity_impl(): move variable definitions to their first assignment. * Fix test_posix.test_sched_getaffinity(): restore the old CPU mask when the test completes! * Doc: Specify that os.cpu_count() counts *logicial* CPUs. * Doc: Specify that os.sched_getaffinity(0) is related to the calling thread.
803f717
to
74ed3db
Compare
I made a few further changes to clarify differences between cpu_count(), process_cpu_count() and sched_affinity(). |
I created issue GH-110160 for this bug. |
Unrelated CI failures:
|
|
if you're inclined to do so, the documentation improvements to the existing APIs in this PR would be worthwhile backporting to the 3.12 branch so that they show up on the default /3/ docs on python.org soon. |
It makes sense. I wrote PR gh-110169 for Python 3.12 (and added "backport to 3.11" label). |
* Refactor os_sched_getaffinity_impl(): move variable definitions to their first assignment. * Fix test_posix.test_sched_getaffinity(): restore the old CPU mask when the test completes! * Doc: Specify that os.cpu_count() counts *logicial* CPUs. * Doc: Specify that os.sched_getaffinity(0) is related to the calling thread.
📚 Documentation preview 📚: https://cpython-previews--109907.org.readthedocs.build/