Cómo averiguar la cantidad de CPU usando Python
Quiero saber la cantidad de CPU en la máquina local que usa Python. El resultado debería ser user/real
el mismo que se muestra time(1)
cuando se llama con un programa de espacio de usuario de escala óptima.
Si tiene Python con una versión >= 2.6, simplemente puede usar
import multiprocessing
multiprocessing.cpu_count()
http://docs.python.org/library/multiprocessing.html#multiprocessing.cpu_count
Si está interesado en la cantidad de procesadores disponibles para su proceso actual, primero debe verificar cpuset . De lo contrario (o si cpuset no está en uso), multiprocessing.cpu_count()
es el camino a seguir en Python 2.6 y versiones posteriores. El siguiente método recurre a un par de métodos alternativos en versiones anteriores de Python:
import os
import re
import subprocess
def available_cpu_count():
""" Number of available virtual or physical CPUs on this system, i.e.
user/real as output by time(1) when called with an optimally scaling
userspace-only program"""
# cpuset
# cpuset may restrict the number of *available* processors
try:
m = re.search(r'(?m)^Cpus_allowed:\s*(.*)$',
open('/proc/self/status').read())
if m:
res = bin(int(m.group(1).replace(',', ''), 16)).count('1')
if res > 0:
return res
except IOError:
pass
# Python 2.6+
try:
import multiprocessing
return multiprocessing.cpu_count()
except (ImportError, NotImplementedError):
pass
# https://github.com/giampaolo/psutil
try:
import psutil
return psutil.cpu_count() # psutil.NUM_CPUS on old versions
except (ImportError, AttributeError):
pass
# POSIX
try:
res = int(os.sysconf('SC_NPROCESSORS_ONLN'))
if res > 0:
return res
except (AttributeError, ValueError):
pass
# Windows
try:
res = int(os.environ['NUMBER_OF_PROCESSORS'])
if res > 0:
return res
except (KeyError, ValueError):
pass
# jython
try:
from java.lang import Runtime
runtime = Runtime.getRuntime()
res = runtime.availableProcessors()
if res > 0:
return res
except ImportError:
pass
# BSD
try:
sysctl = subprocess.Popen(['sysctl', '-n', 'hw.ncpu'],
stdout=subprocess.PIPE)
scStdout = sysctl.communicate()[0]
res = int(scStdout)
if res > 0:
return res
except (OSError, ValueError):
pass
# Linux
try:
res = open('/proc/cpuinfo').read().count('processor\t:')
if res > 0:
return res
except IOError:
pass
# Solaris
try:
pseudoDevices = os.listdir('/devices/pseudo/')
res = 0
for pd in pseudoDevices:
if re.match(r'^cpuid@[0-9]+$', pd):
res += 1
if res > 0:
return res
except OSError:
pass
# Other UNIXes (heuristic)
try:
try:
dmesg = open('/var/run/dmesg.boot').read()
except IOError:
dmesgProcess = subprocess.Popen(['dmesg'], stdout=subprocess.PIPE)
dmesg = dmesgProcess.communicate()[0]
res = 0
while '\ncpu' + str(res) + ':' in dmesg:
res += 1
if res > 0:
return res
except OSError:
pass
raise Exception('Can not determine number of CPUs on this system')
len(os.sched_getaffinity(0))
es lo que normalmente quieres
https://docs.python.org/3/library/os.html#os.sched_getaffinity
os.sched_getaffinity(0)
(agregado en Python 3) devuelve el conjunto de CPU disponibles considerando la sched_setaffinity
llamada al sistema Linux , que limita en qué CPU se puede ejecutar un proceso y sus hijos.
0
significa obtener el valor para el proceso actual. La función devuelve una set()
cantidad de CPU permitidas, de ahí la necesidad de len()
.
multiprocessing.cpu_count()
y os.cpu_count()
por otro lado simplemente devuelve el número total de CPU lógicas, es decir, por ejemplo, el número de CPU considerando hyperthreading .
La diferencia es especialmente importante porque ciertos sistemas de administración de clústeres, como Platform LSF, limitan el uso de CPU del trabajo con sched_getaffinity
.
Por lo tanto, si usa multiprocessing.cpu_count()
, su secuencia de comandos podría intentar usar muchos más núcleos de los que tiene disponibles, lo que puede provocar sobrecargas y tiempos de espera.
Podemos ver la diferencia concretamente restringiendo la afinidad con la taskset
utilidad, lo que nos permite controlar la afinidad de un proceso.
taskset
Ejemplo mínimo
Por ejemplo, si restrinjo Python a solo 1 núcleo (núcleo 0) en mi sistema de 16 núcleos:
taskset -c 0 ./main.py
con el script de prueba:
principal.py
#!/usr/bin/env python3
import multiprocessing
import os
print(multiprocessing.cpu_count())
print(os.cpu_count())
print(len(os.sched_getaffinity(0)))
entonces la salida es:
16
16
1
contranproc
nproc
respeta la afinidad por defecto y:
taskset -c 0 nproc
salidas:
1
y man nproc
lo hace bastante explícito:
imprimir el número de unidades de procesamiento disponibles
Por tanto, len(os.sched_getaffinity(0))
se comporta como nproc
por defecto.
nproc
tiene la --all
bandera para el caso menos común en el que desea obtener el recuento de CPU física sin considerar el conjunto de tareas:
taskset -c 0 nproc --all
os.cpu_count
documentación
La documentación de os.cpu_count
también menciona brevemente esto https://docs.python.org/3.8/library/os.html#os.cpu_count
Este número no es equivalente al número de CPU que puede utilizar el proceso actual. El número de CPU utilizables se puede obtener con
len(os.sched_getaffinity(0))
El mismo comentario también se copia en la documentación de multiprocessing.cpu_count
: https://docs.python.org/3/library/multiprocessing.html#multiprocessing.cpu_count
Desde la fuente 3.8 Lib/multiprocessing/context.py
también vemos que multiprocessing.cpu_count
solo se reenvía a os.cpu_count
, excepto que multiprocessing
uno arroja una excepción en lugar de devolver Ninguno si os.cpu_count
falla:
def cpu_count(self):
'''Returns the number of CPUs in the system'''
num = os.cpu_count()
if num is None:
raise NotImplementedError('cannot determine number of cpus')
else:
return num
3.8 disponibilidad: sistemas con sched_getaffinity
función nativa
El único inconveniente de esto os.sched_getaffinity
es que parece ser UNIX solo a partir de Python 3.8.
cpython 3.8 parece simplemente intentar compilar un pequeño C hola mundo con una sched_setaffinity
llamada de función durante el tiempo de configuración, y si no está presente, HAVE_SCHED_SETAFFINITY
no está configurado y es probable que falte la función:
- https://github.com/python/cpython/blob/v3.8.5/configure#L11523
- https://github.com/python/cpython/blob/v3.8.5/Modules/posixmodule.c#L6457
psutil.Process().cpu_affinity()
: versión de terceros con un puerto de Windows
psutil
El paquete de terceros ( pip install psutil
) se mencionó en: https://stackoverflow.com/a/14840102/895245 pero no la cpu_affinity
función: https://psutil.readthedocs.io/en/latest/#psutil.Process.cpu_affinity
Uso:
import psutil
print(len(psutil.Process().cpu_affinity()))
Esta función hace lo mismo que la biblioteca estándar os.sched_getaffinity
en Linux, pero también la han implementado para Windows haciendo una llamada a la GetProcessAffinityMask
función API de Windows:
- https://github.com/giampaolo/psutil/blob/ee60bad610822a7f630c52922b4918e684ba7695/psutil/_psutil_windows.c#L1112
- https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-getprocessaffinitymask
En otras palabras: esos usuarios de Windows tienen que dejar de ser vagos y enviar un parche a la stdlib ascendente :-)
Probado en Ubuntu 16.04, Python 3.5.2.
Otra opción es utilizar la psutil
biblioteca, que siempre resulta útil en estas situaciones:
>>> import psutil
>>> psutil.cpu_count()
2
Esto debería funcionar en cualquier plataforma compatible con psutil
(Unix y Windows).
Tenga en cuenta que en algunas ocasiones multiprocessing.cpu_count
puede aumentar un NotImplementedError
tiempo psutil
y podrá obtener la cantidad de CPU. Esto se debe simplemente a que psutil
primero intenta utilizar las mismas técnicas que utiliza multiprocessing
y, si fallan, también utiliza otras técnicas.
En Python 3.4+: os.cpu_count() .
multiprocessing.cpu_count()
se implementa en términos de esta función pero aumenta NotImplementedError
si os.cpu_count()
regresa None
("no se puede determinar el número de CPU").