Skip to content

Commit 70b6f21

Browse files
authored
FIX cpu_count on linux and windows (#425)
1 parent fee5e51 commit 70b6f21

File tree

4 files changed

+88
-26
lines changed

4 files changed

+88
-26
lines changed

.github/workflows/test.yml

+2
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ jobs:
5252
- name: windows-py39
5353
os: windows-latest
5454
PYTHON_VERSION: "3.9"
55+
LOKY_TEST_NO_CIM: "true"
5556

5657
- name: macos-py311
5758
os: macos-latest
@@ -63,6 +64,7 @@ jobs:
6364
- name: linux-py311
6465
os: ubuntu-latest
6566
PYTHON_VERSION: "3.11"
67+
LOKY_TEST_NO_LSCPU: "true"
6668
- name: linux-py39-joblib-tests
6769
os: ubuntu-latest
6870
PYTHON_VERSION: "3.9"

CHANGES.md

+5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
### 3.5.0 - in development
22

3+
4+
- Fix detection of the number of physical cores in
5+
`cpu_count(only_physical_cores=True)` on some Linux systems and recent
6+
Windows versions. (#425)
7+
38
- Drop support for Python 3.7 and Python 3.8. (#409)
49

510
- Drop support for PyPy. (#427)

loky/backend/context.py

+57-25
Original file line numberDiff line numberDiff line change
@@ -242,33 +242,11 @@ def _count_physical_cores():
242242
# Not cached yet, find it
243243
try:
244244
if sys.platform == "linux":
245-
cpu_info = subprocess.run(
246-
"lscpu --parse=core".split(), capture_output=True, text=True
247-
)
248-
cpu_info = cpu_info.stdout.splitlines()
249-
cpu_info = {line for line in cpu_info if not line.startswith("#")}
250-
cpu_count_physical = len(cpu_info)
245+
cpu_count_physical = _count_physical_cores_linux()
251246
elif sys.platform == "win32":
252-
cpu_info = subprocess.run(
253-
"wmic CPU Get NumberOfCores /Format:csv".split(),
254-
capture_output=True,
255-
text=True,
256-
)
257-
cpu_info = cpu_info.stdout.splitlines()
258-
cpu_info = [
259-
l.split(",")[1]
260-
for l in cpu_info
261-
if (l and l != "Node,NumberOfCores")
262-
]
263-
cpu_count_physical = sum(map(int, cpu_info))
247+
cpu_count_physical = _count_physical_cores_win32()
264248
elif sys.platform == "darwin":
265-
cpu_info = subprocess.run(
266-
"sysctl -n hw.physicalcpu".split(),
267-
capture_output=True,
268-
text=True,
269-
)
270-
cpu_info = cpu_info.stdout
271-
cpu_count_physical = int(cpu_info)
249+
cpu_count_physical = _count_physical_cores_darwin()
272250
else:
273251
raise NotImplementedError(f"unsupported platform: {sys.platform}")
274252

@@ -286,6 +264,60 @@ def _count_physical_cores():
286264
return cpu_count_physical, exception
287265

288266

267+
def _count_physical_cores_linux():
268+
try:
269+
cpu_info = subprocess.run(
270+
"lscpu --parse=core".split(), capture_output=True, text=True
271+
)
272+
cpu_info = cpu_info.stdout.splitlines()
273+
cpu_info = {line for line in cpu_info if not line.startswith("#")}
274+
return len(cpu_info)
275+
except:
276+
pass # fallback to /proc/cpuinfo
277+
278+
cpu_info = subprocess.run(
279+
"cat /proc/cpuinfo".split(), capture_output=True, text=True
280+
)
281+
cpu_info = cpu_info.stdout.splitlines()
282+
cpu_info = {line for line in cpu_info if line.startswith("core id")}
283+
return len(cpu_info)
284+
285+
286+
def _count_physical_cores_win32():
287+
try:
288+
cmd = "-Command (Get-CimInstance -ClassName Win32_Processor).NumberOfCores"
289+
cpu_info = subprocess.run(
290+
f"powershell.exe {cmd}".split(),
291+
capture_output=True,
292+
text=True,
293+
)
294+
cpu_info = cpu_info.stdout.splitlines()
295+
return int(cpu_info[0])
296+
except:
297+
pass # fallback to wmic (older Windows versions; deprecated now)
298+
299+
cpu_info = subprocess.run(
300+
"wmic CPU Get NumberOfCores /Format:csv".split(),
301+
capture_output=True,
302+
text=True,
303+
)
304+
cpu_info = cpu_info.stdout.splitlines()
305+
cpu_info = [
306+
l.split(",")[1] for l in cpu_info if (l and l != "Node,NumberOfCores")
307+
]
308+
return sum(map(int, cpu_info))
309+
310+
311+
def _count_physical_cores_darwin():
312+
cpu_info = subprocess.run(
313+
"sysctl -n hw.physicalcpu".split(),
314+
capture_output=True,
315+
text=True,
316+
)
317+
cpu_info = cpu_info.stdout
318+
return int(cpu_info)
319+
320+
289321
class LokyContext(BaseContext):
290322
"""Context relying on the LokyProcess."""
291323

tests/test_loky_module.py

+24-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import os
33
import sys
44
import shutil
5+
import subprocess
56
import tempfile
67
import warnings
78
from subprocess import check_output
@@ -19,7 +20,29 @@ def test_version():
1920
), "There are no __version__ argument on the loky module"
2021

2122

22-
def test_cpu_count():
23+
def test_cpu_count(monkeypatch):
24+
25+
# Monkeypatch subprocess.run to simulate the absence of lscpu on linux or CIM on
26+
# windows to test the different code paths in _cpu_count_physical.
27+
old_run = subprocess.run
28+
29+
def mock_run(*args, **kwargs):
30+
if (
31+
"lscpu" in args[0]
32+
and os.environ.get("LOKY_TEST_NO_LSCPU") == "true"
33+
):
34+
raise RuntimeError("lscpu not available")
35+
36+
if (
37+
"powershell.exe" in args[0]
38+
and os.environ.get("LOKY_TEST_NO_CIM") == "true"
39+
):
40+
raise RuntimeError("Cim not available")
41+
42+
return old_run(*args, **kwargs)
43+
44+
monkeypatch.setattr(subprocess, "run", mock_run)
45+
2346
cpus = cpu_count()
2447
assert type(cpus) is int
2548
assert cpus >= 1

0 commit comments

Comments
 (0)