El rendimiento del multiprocesamiento de Python solo mejora con la raíz cuadrada del número de núcleos utilizados

Estoy intentando implementar el multiprocesamiento en Python (Windows Server 2012) y estoy teniendo problemas para lograr el grado de mejora de rendimiento que espero. En particular, para un conjunto de tareas que son casi completamente independientes, esperaría una mejora lineal con núcleos adicionales .


Entiendo que, especialmente en Windows, existe una sobrecarga en la apertura de nuevos procesos [1] , y que muchas de las peculiaridades del código subyacente pueden interferir en una tendencia limpia. Pero, en teoría, la tendencia en última instancia aún debería ser casi lineal para una tarea completamente paralelizada [2] ; o quizás logístico si estuviera tratando con una tarea parcialmente en serie [3] .

Sin embargo, cuando ejecuto multiprocessing.Pool en una función de prueba de comprobación principal (código a continuación), obtengo una relación de raíz cuadrada casi perfecta hasta N_cores=36 (el número de núcleos físicos en mi servidor) antes de que llegue el rendimiento esperado cuando Me meto en los núcleos lógicos adicionales.


Aquí hay una gráfica de los resultados de mi prueba de rendimiento: introduzca la descripción de la imagen aquí
(” Rendimiento normalizado ” es [ un tiempo de ejecución con 1 núcleo de CPU ] dividido por [ un tiempo de ejecución con N núcleos de CPU ] ).

¿Es normal tener esta disminución dramática de rendimientos con multiprocesamiento? ¿O me estoy perdiendo algo con mi implementación?


 import numpy as np from multiprocessing import Pool, cpu_count, Manager import math as m from functools import partial from time import time def check_prime(num): #Assert positive integer value if num!=m.floor(num) or num<1: print("Input must be a positive integer") return None #Check divisibility for all possible factors prime = True for i in range(2,num): if num%i==0: prime=False return prime def cp_worker(num, L): prime = check_prime(num) L.append((num, prime)) def mp_primes(omag, mp=cpu_count()): with Manager() as manager: np.random.seed(0) numlist = np.random.randint(10**omag, 10**(omag+1), 100) L = manager.list() cp_worker_ptl = partial(cp_worker, L=L) try: pool = Pool(processes=mp) list(pool.imap(cp_worker_ptl, numlist)) except Exception as e: print(e) finally: pool.close() # no more tasks pool.join() return L if __name__ == '__main__': rt = [] for i in range(cpu_count()): t0 = time() mp_result = mp_primes(6, mp=i+1) t1 = time() rt.append(t1-t0) print("Using %i core(s), run time is %.2fs" % (i+1, rt[-1])) 

Nota: soy consciente de que para esta tarea probablemente sería más eficiente implementar subprocesos múltiples, pero el script real para el cual este es un análogo simplificado es incompatible con los subprocesos múltiples de Python debido a GIL.

@KellanM merecía [+1] por el monitoreo cuantitativo del rendimiento

¿Me estoy perdiendo algo con mi implementación?

Sí, se abstrae de todos los costos adicionales de la gestión de procesos.

Si bien ha expresado la expectativa de una mejora lineal con núcleos adicionales “, esto difícilmente aparecerá en la práctica por varias razones (incluso la exageración del comunismo no pudo ofrecer nada de forma gratuita).

Gene AMDAHL ha formulado la ley inicial de rendimientos decrecientes . introduzca la descripción de la imagen aquí
Una versión más reciente y reformulada también tuvo en cuenta los efectos de la gestión de procesos {setup | terminate}: costos adicionales de costos adicionales y trató de hacer frente a la atomicidad del procesamiento (dado que las grandes cargas útiles de paquetes de trabajo no se pueden recuperar fácilmente) ubicado / redistribuido sobre el conjunto disponible de núcleos de CPU libres en la mayoría de los sistemas de progtwigción comunes (excepto en algunos casos de arte de micro-progtwigción específica, como el demostrado en PARLANSE de Semantic Design o SISAL de LLNL que se ha mostrado tan colorido en el pasado).


¿Un mejor siguiente paso?

Si está realmente interesado en este dominio, siempre se pueden medir y comparar experimentalmente los costos reales de la gestión de procesos (más los costos de flujo de datos, más los costos de asignación de memoria, … hasta la terminación del proceso y el reensamblado de los resultados en la parte principal proceso) para registrar cuantitativamente de manera justa y evaluar la relación costo / beneficio adicional del uso de más núcleos de CPU (que en python restablecerán todo el estado del intérprete de Python, incluida toda su memoria de estado antes). una primera operación útil se llevará a cabo en un primer proceso generado y de configuración).

Bajo desempeño (para el caso anterior abajo)
Si no son efectos desastrosos (del último caso más abajo),
de cualquiera de las políticas mal diseñadas de mapeo de recursos, ya sea
una ” reserva de reserva ” -recursos de un grupo de CPU -cores
o
un ” exceso de reserva ” -recursos de un grupo de RAM -space
se discuten también aquí

El enlace a la Ley de Amdahl reformulada anteriormente le ayudará a evaluar el punto de los rendimientos decrecientes, para no pagar más de lo que nunca recibirá.

Los experimentos de Hoefinger et Haunschmid pueden servir como una buena evidencia práctica de cómo un número creciente de nodos de procesamiento (ya sea un núcleo de CPU administrado por O / S local, o un nodo de architecture distribuida NUMA) comenzará a disminuir el rendimiento resultante.
donde un punto de rendimientos decrecientes (demostrado en la ley de Amdahl agnóstica general)
en realidad comenzará a convertirse en un Punto después del cual pagará más de lo que reciba. :

introduzca la descripción de la imagen aquí ¡Buena suerte en este interesante campo! introduzca la descripción de la imagen aquí


Por último, si bien no menos importante,

Los problemas NUMA / no locales hacen que se escuche su voz, en la discusión sobre el escalado para las estrategias informáticas sintonizadas de grado HPC (en caché / en memoria RAM) y puede, como efecto secundario, ayudar a detectar las fallas (según lo informado por @ eryksun arriba). Uno puede sentirse libre para revisar la topología NUMA real de su plataforma utilizando la herramienta lstopo , para ver la abstracción con la que intenta trabajar el sistema operativo, una vez que se progtwig la ejecución de la tarea “solo” – [CONCURRENT] sobre dichos recursos NUMA -topología:

introduzca la descripción de la imagen aquí