Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 69c8bf4

Browse files
committed
gh-109649: Add os.process_cpu_count() function
* Fix test_posix.test_sched_getaffinity(): restore the old CPU mask when the test completes! * Doc: Specify that os.cpu_count() counts *logicial* CPUs.
1 parent 19bf398 commit 69c8bf4

File tree

7 files changed

+169
-45
lines changed

7 files changed

+169
-45
lines changed

Doc/library/os.rst

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5183,12 +5183,10 @@ Miscellaneous System Information
51835183

51845184
.. function:: cpu_count()
51855185

5186-
Return the number of CPUs in the system. Returns ``None`` if undetermined.
5187-
5188-
This number is not equivalent to the number of CPUs the current process can
5189-
use. The number of usable CPUs can be obtained with
5190-
``len(os.sched_getaffinity(0))``
5186+
Return the number of logical CPUs in the system. Returns ``None`` if
5187+
undetermined.
51915188

5189+
See also the :func:`process_cpu_count` function.
51925190

51935191
.. versionadded:: 3.4
51945192

@@ -5202,6 +5200,17 @@ Miscellaneous System Information
52025200
.. availability:: Unix.
52035201

52045202

5203+
.. function:: process_cpu_count()
5204+
5205+
Get the number of logical CPUs usable by the current process. Returns
5206+
``None`` if undetermined. It can be less than :func:`cpu_count` depending on
5207+
the process affinity.
5208+
5209+
See also the :func:`cpu_count` function.
5210+
5211+
.. versionadded:: 3.13
5212+
5213+
52055214
.. function:: sysconf(name, /)
52065215

52075216
Return integer-valued system configuration values. If the configuration value

Doc/whatsnew/3.13.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,13 @@ opcode
163163
documented or exposed through ``dis``, and were not intended to be
164164
used externally.
165165

166+
os
167+
--
168+
169+
* Add :func:`os.process_cpu_count` function to get the number of logical CPUs
170+
usable by the current process.
171+
(Contributed by Victor Stinner in :gh:`109649`.)
172+
166173
pathlib
167174
-------
168175

Lib/test/test_os.py

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3996,14 +3996,42 @@ def test_oserror_filename(self):
39963996
self.fail(f"No exception thrown by {func}")
39973997

39983998
class CPUCountTests(unittest.TestCase):
3999+
def check_cpu_count(self, cpus):
4000+
if cpus is None:
4001+
self.skipTest("Could not determine the number of CPUs")
4002+
4003+
self.assertIsInstance(cpus, int)
4004+
self.assertGreater(cpus, 0)
4005+
39994006
def test_cpu_count(self):
40004007
cpus = os.cpu_count()
4001-
if cpus is not None:
4002-
self.assertIsInstance(cpus, int)
4003-
self.assertGreater(cpus, 0)
4004-
else:
4008+
self.check_cpu_count(cpus)
4009+
4010+
def test_process_cpu_count(self):
4011+
cpus = os.process_cpu_count()
4012+
self.assertLessEqual(cpus, os.cpu_count())
4013+
self.check_cpu_count(cpus)
4014+
4015+
@unittest.skipUnless(hasattr(os, 'sched_setaffinity'),
4016+
"don't have sched affinity support")
4017+
def test_process_cpu_count_affinity(self):
4018+
ncpu = os.cpu_count()
4019+
if ncpu is None:
40054020
self.skipTest("Could not determine the number of CPUs")
40064021

4022+
# Disable one CPU
4023+
mask = os.sched_getaffinity(0)
4024+
if len(mask) <= 1:
4025+
self.skipTest(f"sched_getaffinity() returns less than "
4026+
f"2 CPUs: {sorted(mask)}")
4027+
self.addCleanup(os.sched_setaffinity, 0, list(mask))
4028+
mask.pop()
4029+
os.sched_setaffinity(0, mask)
4030+
4031+
# test process_cpu_count()
4032+
affinity = os.process_cpu_count()
4033+
self.assertEqual(affinity, ncpu - 1)
4034+
40074035

40084036
# FD inheritance check is only useful for systems with process support.
40094037
@support.requires_subprocess()

Lib/test/test_posix.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1205,6 +1205,7 @@ def test_sched_getaffinity(self):
12051205
@requires_sched_affinity
12061206
def test_sched_setaffinity(self):
12071207
mask = posix.sched_getaffinity(0)
1208+
self.addCleanup(posix.sched_setaffinity, 0, list(mask))
12081209
if len(mask) > 1:
12091210
# Empty masks are forbidden
12101211
mask.pop()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add :func:`os.process_cpu_count` function to get the number of logical CPUs
2+
usable by the current process. Patch by Victor Stinner.

Modules/clinic/posixmodule.c.h

Lines changed: 23 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Modules/posixmodule.c

Lines changed: 90 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -8138,39 +8138,45 @@ static PyObject *
81388138
os_sched_getaffinity_impl(PyObject *module, pid_t pid)
81398139
/*[clinic end generated code: output=f726f2c193c17a4f input=983ce7cb4a565980]*/
81408140
{
8141-
int cpu, ncpus, count;
8141+
int ncpus = NCPUS_START;
81428142
size_t setsize;
8143-
cpu_set_t *mask = NULL;
8144-
PyObject *res = NULL;
8143+
cpu_set_t *mask;
81458144

8146-
ncpus = NCPUS_START;
81478145
while (1) {
81488146
setsize = CPU_ALLOC_SIZE(ncpus);
81498147
mask = CPU_ALLOC(ncpus);
8150-
if (mask == NULL)
8148+
if (mask == NULL) {
81518149
return PyErr_NoMemory();
8152-
if (sched_getaffinity(pid, setsize, mask) == 0)
8150+
}
8151+
if (sched_getaffinity(pid, setsize, mask) == 0) {
81538152
break;
8153+
}
81548154
CPU_FREE(mask);
8155-
if (errno != EINVAL)
8155+
if (errno != EINVAL) {
81568156
return posix_error();
8157+
}
81578158
if (ncpus > INT_MAX / 2) {
8158-
PyErr_SetString(PyExc_OverflowError, "could not allocate "
8159-
"a large enough CPU set");
8159+
PyErr_SetString(PyExc_OverflowError,
8160+
"could not allocate a large enough CPU set");
81608161
return NULL;
81618162
}
8162-
ncpus = ncpus * 2;
8163+
ncpus *= 2;
81638164
}
81648165

8165-
res = PySet_New(NULL);
8166-
if (res == NULL)
8166+
PyObject *res = PySet_New(NULL);
8167+
if (res == NULL) {
81678168
goto error;
8168-
for (cpu = 0, count = CPU_COUNT_S(setsize, mask); count; cpu++) {
8169+
}
8170+
8171+
int cpu = 0;
8172+
int count = CPU_COUNT_S(setsize, mask);
8173+
for (; count; cpu++) {
81698174
if (CPU_ISSET_S(cpu, setsize, mask)) {
81708175
PyObject *cpu_num = PyLong_FromLong(cpu);
81718176
--count;
8172-
if (cpu_num == NULL)
8177+
if (cpu_num == NULL) {
81738178
goto error;
8179+
}
81748180
if (PySet_Add(res, cpu_num)) {
81758181
Py_DECREF(cpu_num);
81768182
goto error;
@@ -8182,12 +8188,12 @@ os_sched_getaffinity_impl(PyObject *module, pid_t pid)
81828188
return res;
81838189

81848190
error:
8185-
if (mask)
8191+
if (mask) {
81868192
CPU_FREE(mask);
8193+
}
81878194
Py_XDECREF(res);
81888195
return NULL;
81898196
}
8190-
81918197
#endif /* HAVE_SCHED_SETAFFINITY */
81928198

81938199
#endif /* HAVE_SCHED_H */
@@ -14338,44 +14344,96 @@ os_get_terminal_size_impl(PyObject *module, int fd)
1433814344
/*[clinic input]
1433914345
os.cpu_count
1434014346
14341-
Return the number of CPUs in the system; return None if indeterminable.
14347+
Return the number of logical CPUs in the system.
1434214348
14343-
This number is not equivalent to the number of CPUs the current process can
14344-
use. The number of usable CPUs can be obtained with
14345-
``len(os.sched_getaffinity(0))``
14349+
Return None if indeterminable.
1434614350
[clinic start generated code]*/
1434714351

1434814352
static PyObject *
1434914353
os_cpu_count_impl(PyObject *module)
14350-
/*[clinic end generated code: output=5fc29463c3936a9c input=e7c8f4ba6dbbadd3]*/
14354+
/*[clinic end generated code: output=5fc29463c3936a9c input=ba2f6f8980a0e2eb]*/
1435114355
{
14352-
int ncpu = 0;
14356+
int ncpu;
1435314357
#ifdef MS_WINDOWS
14354-
#ifdef MS_WINDOWS_DESKTOP
14358+
# ifdef MS_WINDOWS_DESKTOP
1435514359
ncpu = GetActiveProcessorCount(ALL_PROCESSOR_GROUPS);
14356-
#endif
14360+
# else
14361+
ncpu = 0;
14362+
# endif
14363+
1435714364
#elif defined(__hpux)
1435814365
ncpu = mpctl(MPC_GETNUMSPUS, NULL, NULL);
14366+
1435914367
#elif defined(HAVE_SYSCONF) && defined(_SC_NPROCESSORS_ONLN)
1436014368
ncpu = sysconf(_SC_NPROCESSORS_ONLN);
14369+
1436114370
#elif defined(__VXWORKS__)
1436214371
ncpu = _Py_popcount32(vxCpuEnabledGet());
14372+
1436314373
#elif defined(__DragonFly__) || \
1436414374
defined(__OpenBSD__) || \
1436514375
defined(__FreeBSD__) || \
1436614376
defined(__NetBSD__) || \
1436714377
defined(__APPLE__)
14368-
int mib[2];
14378+
ncpu = 0;
1436914379
size_t len = sizeof(ncpu);
14370-
mib[0] = CTL_HW;
14371-
mib[1] = HW_NCPU;
14372-
if (sysctl(mib, 2, &ncpu, &len, NULL, 0) != 0)
14380+
int mib[2] = {CTL_HW, HW_NCPU};
14381+
if (sysctl(mib, 2, &ncpu, &len, NULL, 0) != 0) {
1437314382
ncpu = 0;
14383+
}
1437414384
#endif
14375-
if (ncpu >= 1)
14376-
return PyLong_FromLong(ncpu);
14377-
else
14385+
14386+
if (ncpu < 1) {
1437814387
Py_RETURN_NONE;
14388+
}
14389+
return PyLong_FromLong(ncpu);
14390+
}
14391+
14392+
14393+
/*[clinic input]
14394+
os.process_cpu_count
14395+
14396+
Get the number of logical CPUs usable by the current process.
14397+
14398+
Return None if indeterminable.
14399+
[clinic start generated code]*/
14400+
14401+
static PyObject *
14402+
os_process_cpu_count_impl(PyObject *module)
14403+
/*[clinic end generated code: output=dc750a336e010b50 input=b9b63acbae0dbe45]*/
14404+
{
14405+
#if defined(HAVE_SCHED_H) && defined(HAVE_SCHED_SETAFFINITY)
14406+
int ncpus = NCPUS_START;
14407+
cpu_set_t *mask;
14408+
size_t setsize;
14409+
14410+
while (1) {
14411+
setsize = CPU_ALLOC_SIZE(ncpus);
14412+
mask = CPU_ALLOC(ncpus);
14413+
if (mask == NULL) {
14414+
return PyErr_NoMemory();
14415+
}
14416+
if (sched_getaffinity(0, setsize, mask) == 0) {
14417+
break;
14418+
}
14419+
CPU_FREE(mask);
14420+
if (errno != EINVAL) {
14421+
return posix_error();
14422+
}
14423+
if (ncpus > INT_MAX / 2) {
14424+
PyErr_SetString(PyExc_OverflowError,
14425+
"could not allocate a large enough CPU set");
14426+
return NULL;
14427+
}
14428+
ncpus *= 2;
14429+
}
14430+
14431+
int ncpu = CPU_COUNT_S(setsize, mask);
14432+
CPU_FREE(mask);
14433+
return PyLong_FromLong(ncpu);
14434+
#else
14435+
return os_cpu_count_impl(NULL);
14436+
#endif
1437914437
}
1438014438

1438114439

@@ -15972,6 +16030,7 @@ static PyMethodDef posix_methods[] = {
1597216030

1597316031
OS_GET_TERMINAL_SIZE_METHODDEF
1597416032
OS_CPU_COUNT_METHODDEF
16033+
OS_PROCESS_CPU_COUNT_METHODDEF
1597516034
OS_GET_INHERITABLE_METHODDEF
1597616035
OS_SET_INHERITABLE_METHODDEF
1597716036
OS_GET_HANDLE_INHERITABLE_METHODDEF

0 commit comments

Comments
 (0)