Error extraño en Pandas y Numpy con respecto al multihilo

25

La mayoría de las funciones de Numpy permitirán el subprocesamiento múltiple de forma predeterminada.

por ejemplo, trabajo en una estación de trabajo Intel CPU de 8 núcleos, si ejecuto un script

import numpy as np    
x=np.random.random(1000000)
for i in range(100000):
    np.sqrt(x)

Linux topmostrará un uso de CPU del 800% durante la ejecución, lo ingrese la descripción de la imagen aquí que significa que numpy detecta automáticamente que mi estación de trabajo tiene 8 núcleos y np.sqrtusa automáticamente los 8 núcleos para acelerar el cálculo.

Sin embargo, encontré un error extraño. Si ejecuto un script

import numpy as np
import pandas as pd
df=pd.DataFrame(np.random.random((10,10)))
df+df
x=np.random.random(1000000)
for i in range(100000):
    np.sqrt(x)

¡El uso de la CPU es 100%! Esto significa que si además de dos pandas DataFrame antes de ejecutar cualquier función numpy, la función de subprocesamiento automático de numpy desaparece sin previo aviso. Esto no es absolutamente razonable, ¿por qué el cálculo del marco de datos de Pandas afectaría la configuración de subprocesos Numpy? ¿Es un error? ¿Cómo solucionar esto?ingrese la descripción de la imagen aquí


PD:

Excavo más con la perfherramienta Linux .

ejecutando el primer script muestra

ingrese la descripción de la imagen aquí

Mientras se ejecuta el segundo script muestra

ingrese la descripción de la imagen aquí

Por lo tanto libmkl_vml_avx2.so, ambas secuencias de comandos implican , mientras que la primera secuencia de comandos implica adicional libiomp5.soque parece estar relacionada con openMP.

Y dado que vml significa intel vector math library, de acuerdo con vml doc, supongo que al menos las funciones a continuación son multiproceso automáticamente

ingrese la descripción de la imagen aquí

usuario15964
fuente
No estoy seguro de entender tu pregunta. ¿Puedes elaborar?
AMC
@AMC Actualicé mi publicación, espero que ahora esté clara
user15964
Creo que se necesita más información como np, pandas, versión, CPU, tipo de sistema operativo ... No puedo reproducir en mi máquina. No utiliza múltiples CPU en ambos códigos.
hunzter
@hunzter OK, aquí hay información: Ubuntu 16.04.5 LTS numpy 1.17.2 py37haad9e8e_0 pandas 0.25.1 py37he6710b0_0 CPU Intel (R) Xeon (R) E5-1680 v4 @ 3.40GHz. PD. Yo uso anaconda
user15964
1
Podría, por favor compruebe lo siguiente:import numpy as np import pandas as pd import os os.environ["MKL_NUM_THREADS"] = '4' print(os.environ["MKL_NUM_THREADS"]) df=pd.DataFrame(np.random.random((10,10))) df+df print(os.environ["MKL_NUM_THREADS"]) a = np.random.random((20000000, 3)) b = np.random.random((3, 30)) for _ in range(10): c = np.dot(a, b)
Stas Buzuluk

Respuestas:

13

Pandas utiliza numexprdebajo del capó para calcular algunas operaciones y numexprestablece el número máximo de subprocesos para vml en 1, cuando se importa :

# The default for VML is 1 thread (see #39)
set_vml_num_threads(1)

y los pandas lo importan cuando df+dfse evalúa en expressions.py :

from pandas.core.computation.check import _NUMEXPR_INSTALLED

if _NUMEXPR_INSTALLED:
   import numexpr as ne

Sin embargo, la distribución Anaconda también utiliza VML-funcionalidad para funciones tales como sqrt, sin, cosy así sucesivamente - y una vez numexprestablecido el número máximo de VML-hilos para 1, los numpy-funciones ya no uso paralelización.

El problema se puede ver fácilmente en gdb (usando su script lento):

>>> gdb --args python slow.py
(gdb) b mkl_serv_domain_set_num_threads
function "mkl_serv_domain_set_num_threads" not defined.
Make breakpoint pending on future shared library load? (y or [n]) y
Breakpoint 1 (mkl_serv_domain_set_num_threads) pending.
(gbd) run
Thread 1 "python" hit Breakpoint 1, 0x00007fffee65cd70 in mkl_serv_domain_set_num_threads () from /home/ed/anaconda37/lib/python3.7/site-packages/numpy/../../../libmkl_intel_thread.so
(gdb) bt 
#0  0x00007fffee65cd70 in mkl_serv_domain_set_num_threads () from /home/ed/anaconda37/lib/python3.7/site-packages/numpy/../../../libmkl_intel_thread.so
#1  0x00007fffe978026c in _set_vml_num_threads(_object*, _object*) () from /home/ed/anaconda37/lib/python3.7/site-packages/numexpr/interpreter.cpython-37m-x86_64-linux-gnu.so
#2  0x00005555556cd660 in _PyMethodDef_RawFastCallKeywords () at /tmp/build/80754af9/python_1553721932202/work/Objects/call.c:694
...
(gdb) print $rdi
$1 = 1

es decir, podemos ver, numexprestablece el número de subprocesos en 1. Que luego se usa cuando se llama a la función vml-sqrt:

(gbd) b mkl_serv_domain_get_max_threads
Breakpoint 2 at 0x7fffee65a900
(gdb) (gdb) c
Continuing.

Thread 1 "python" hit Breakpoint 2, 0x00007fffee65a900 in mkl_serv_domain_get_max_threads () from /home/ed/anaconda37/lib/python3.7/site-packages/numpy/../../../libmkl_intel_thread.so
(gdb) bt
#0  0x00007fffee65a900 in mkl_serv_domain_get_max_threads () from /home/ed/anaconda37/lib/python3.7/site-packages/numpy/../../../libmkl_intel_thread.so
#1  0x00007ffff01fcea9 in mkl_vml_serv_threader_d_1i_1o () from /home/ed/anaconda37/lib/python3.7/site-packages/numpy/../../../libmkl_intel_thread.so
#2  0x00007fffedf78563 in vdSqrt () from /home/ed/anaconda37/lib/python3.7/site-packages/numpy/../../../libmkl_intel_lp64.so
#3  0x00007ffff5ac04ac in trivial_two_operand_loop () from /home/ed/anaconda37/lib/python3.7/site-packages/numpy/core/_multiarray_umath.cpython-37m-x86_64-linux-gnu.so

Entonces, podemos ver que Numpy utiliza la implementación de vml, vdSqrtque utiliza mkl_vml_serv_threader_d_1i_1opara decidir si el cálculo debe hacerse en paralelo y si se ve el número de subprocesos:

(gdb) fin
Run till exit from #0  0x00007fffee65a900 in mkl_serv_domain_get_max_threads () from /home/ed/anaconda37/lib/python3.7/site-packages/numpy/../../../libmkl_intel_thread.so
0x00007ffff01fcea9 in mkl_vml_serv_threader_d_1i_1o () from /home/ed/anaconda37/lib/python3.7/site-packages/numpy/../../../libmkl_intel_thread.so
(gdb) print $rax
$2 = 1

el registro %rax tiene el número máximo de hilos y es 1.

Ahora podemos usar numexprpara aumentar el número de hilos vml , es decir:

import numpy as np
import numexpr as ne
import pandas as pd
df=pd.DataFrame(np.random.random((10,10)))
df+df

#HERE: reset number of vml-threads
ne.set_vml_num_threads(8)

x=np.random.random(1000000)
for i in range(10000):
    np.sqrt(x)     # now in parallel

¡Ahora se utilizan múltiples núcleos!

Ead
fuente
Muchas gracias! Finalmente, una gran respuesta lo explica todo. Al final, es el numexprdetrás de escena.
user15964
De acuerdo ... buena excavación! Siguiente pregunta, sin embargo ... ¿por qué numexpr push thread cuenta a 1? ¿Posiblemente debido a problemas de inestabilidad / seguridad de subprocesos? En lugar de aumentar el recuento hasta 8, podría ser más seguro pasar a una versión estable y segura de subprocesos de NumPy. Tal vez también sea bueno verificar esta variable con el último y mejor NumPy en caso de que ya no sea necesario, por lo tanto, técnicamente es un error.
Andrew Atrens
@AndrewAtrens puedes echar un vistazo a github.com/pydata/numexpr/issues/39 y github.com/pydata/numexpr/issues/355
ead
2

Mirando numpy, parece que, bajo el capó, ha tenido problemas de activación / desactivación con subprocesos múltiples, y dependiendo de la versión que esté utilizando, puede esperar ver bloqueos cuando sube ne.set_vml_num_threads () ..

http://numpy-discussion.10968.n7.nabble.com/ANN-NumExpr-2-7-0-Release-td47414.html

Necesito entender cómo esto está pegado al intérprete de Python, dado su ejemplo de código donde parece que de alguna manera parece permitir que varias llamadas aparentemente sincrónicas / ordenadas a np.sqrt () procedan en paralelo. Supongo que si el intérprete de Python siempre está devolviendo una referencia a un objeto cuando aparece la pila, y en su ejemplo, solo presenta esas referencias y no las asigna o manipula de ninguna manera, estaría bien. Pero si las iteraciones de bucle posteriores dependen de las anteriores, entonces parece menos claro cómo se podrían paralelizar de forma segura. Podría decirse que la falla silenciosa / los resultados incorrectos son peores que los accidentes.

Andrew Atrens
fuente
Hola, Andrew Atrens, ya casi estás allí. Es el problema de ne.set_vml_num_threads (). Muchas gracias por su tiempo dedicado a mi problema.
user15964
Happy Trails :)
Andrew Atrens
0

Creo que su premisa inicial puede ser incorrecta:

Usted dijo: Lo que significa que numpy detecta automáticamente que mi estación de trabajo tiene 8 núcleos, y np.sqrt usa automáticamente los 8 núcleos para acelerar el cálculo.

Una sola función np.sqrt () no puede adivinar cómo se invocará o devolverá antes de que se haya completado parcialmente. Hay mecanismos de paralelismo en python, pero ninguno es automático.

Ahora, dicho esto, el intérprete de Python puede optimizar el bucle for para el paralelismo, que puede ser lo que está viendo, pero sospecho que si observa el tiempo del reloj de pared para que este bucle se ejecute, no habrá diferente independientemente de si está (aparentemente) usando 8 núcleos o 1 núcleo.

ACTUALIZACIÓN: Después de leer un poco más de los comentarios, parece que el comportamiento multinúcleo que está viendo está relacionado con la distribución anaconda del intérprete de Python. Le eché un vistazo, pero no pude encontrar ningún código fuente, pero parece que la licencia de Python permite a las entidades (como anaconda.com) compilar y distribuir derivados del intérprete sin requerir que se publiquen sus cambios.

Supongo que puede comunicarse con la gente de la anaconda: el comportamiento que está viendo será difícil de entender sin saber qué / si algo han cambiado en el intérprete.

También verifique rápidamente el tiempo del reloj de pared con / sin la optimización para ver si realmente es 8 veces más rápido; incluso si realmente tiene los 8 núcleos funcionando en lugar de 1, sería bueno saber si los resultados son realmente 8x más rápido o si hay bloqueos de giro en uso que todavía se están serializando en un único mutex.

Andrew Atrens
fuente
1
Hola Andrew Atrens. Pero la paralelización no se realiza por python, sino por el backend de anaconda numpy, que es Intel MKL. Y sí, abrí un problema en numpy, me sugirieron que abriera un problema en anaconda, y lo hice. Sin embargo, no recibí una sola respuesta de anaconda durante una semana. Entonces tal vez simplemente ignoraron mi informe ...
user15964
Es un problema con la versión / lanzamiento anaconda del intérprete de python: su versión utiliza openmp mientras que la versión estándar de python no.
Andrew Atrens
Es un problema con la versión / lanzamiento anaconda del intérprete de python: su versión enlaza / utiliza las API de openmp, mientras que el intérprete de lanzamiento de python no. cuando digo utiliza literalmente me refiero a llamar a las funciones de la API de OpenMP 'debajo del capó'. Al igual que con cualquier optimización implícita en la que no podemos ver el código fuente, solo podemos informarlo (como lo ha hecho usted) y, si es posible, intentar solucionarlo.
Andrew Atrens
Otra idea sobre esto ... podría volver a codificar su aplicación para usar explícitamente las bibliotecas multiproceso de Python y no confiar en el optimizador del intérprete para que lo haga por usted ... Estoy pensando en grupos de subprocesos ... dependiendo de lo complicada que sea su aplicación, y si este no es tu primer paso en la programación de subprocesos, puede que no sea demasiado difícil. Para mantener el uso de la portabilidad, probablemente deberías tratar de evitar algo específico de anaconda o openmp: te lo dejaré a ti ya que no tengo tiempo cavar en él ... :) De todos modos, la mejor de las suertes y espero que esto ayude a eliminar la niebla de lo que estás viendo. :) :)
Andrew Atrens