Strano bug in Panda e Numpy per quanto riguarda il multithreading


25

La maggior parte della funzione di Numpy abiliterà il multithreading per impostazione predefinita.

ad esempio, lavoro su una stazione di lavoro Intel a 8 core, se eseguo uno script

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

Linux topmostrerà un utilizzo della CPU dell'800% durante l'esecuzione come il inserisci qui la descrizione dell'immagine che significa che numpy rileva automaticamente che la mia workstation ha 8 core e np.sqrtusa automaticamente tutti e 8 i core per accelerare il calcolo.

Tuttavia, ho trovato un bug strano. Se eseguo uno 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)

l'utilizzo della cpu è del 100% !!. Significa che se si aggiungono due Panda DataFrame prima di eseguire qualsiasi funzione numpy, la funzione di multithreading automatico di numpy scompare senza alcun preavviso! Questo non è assolutamente ragionevole, perché il calcolo di Pandas dataFrame influirebbe sull'impostazione del thread Numpy? E 'un errore? Come aggirare questo?inserisci qui la descrizione dell'immagine


PS:

Scavo ulteriormente usando lo perfstrumento Linux .

eseguendo i primi spettacoli di script

inserisci qui la descrizione dell'immagine

Durante l'esecuzione del secondo script mostra

inserisci qui la descrizione dell'immagine

Quindi entrambi gli script coinvolgono libmkl_vml_avx2.so, mentre il primo script prevede ulteriori libiomp5.soche sembrano essere correlati a openMP.

E poiché vml significa libreria matematica vettoriale Intel, quindi secondo il documento vml immagino che almeno le funzioni seguenti siano tutte automaticamente multithread

inserisci qui la descrizione dell'immagine


Non sono sicuro di aver capito la tua domanda. Puoi elaborare?
AMC

@AMC Ho aggiornato il mio post, spero che ora sia chiaro
user15964,

Penso che siano necessarie ulteriori informazioni come np, panda, versione, CPU, tipo di sistema operativo ... Non riesco a riprodurre sulla mia macchina. Non utilizza più CPU in entrambi i codici.
hunzter,

@hunzter OK, ecco le informazioni: Ubuntu 16.04.5 LTS numpy 1.17.2 py37haad9e8e_0 panda 0.25.1 py37he6710b0_0 Intel (R) Xeon (R) CPU E5-1680 v4 @ 3.40GHz. PS. Uso anaconda
user15964 il

1
Potresti controllare questo: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, il

Risposte:


13

Pandas usa numexprsotto il cofano per calcolare alcune operazioni e numexprimposta il numero massimo di thread per vml su 1, quando viene importato :

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

e viene importato dai panda quando df+dfviene valutato in expressions.py :

from pandas.core.computation.check import _NUMEXPR_INSTALLED

if _NUMEXPR_INSTALLED:
   import numexpr as ne

Tuttavia, la distribuzione Anaconda utilizza anche VML-funzionalità per funzioni quali sqrt, sin, cose così via - e una volta numexprimpostare il numero massimo di VML-thread per 1, le funzioni NumPy uso più parallelizzazione.

Il problema può essere facilmente visto in gdb (usando lo 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

cioè possiamo vedere, numexprimposta il numero di thread su 1. Che viene successivamente usato quando viene chiamata la funzione 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

Quindi possiamo vedere che numpy usa l'implementazione di vml vdSqrtche utilizza mkl_vml_serv_threader_d_1i_1oper decidere se il calcolo deve essere fatto in parallelo e sembra il numero di thread:

(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

il registro %raxha il numero massimo di thread ed è 1.

Ora possiamo usare numexprper aumentare il numero di thread vml , ovvero:

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

Ora vengono utilizzati più core!


Grazie mille! Infine, un'ottima risposta spiega tutto. Alla fine, è il numexprdietro le quinte.
user15964

D'accordo ... buon scavo! La prossima domanda però ... perché numexpr il thread push conta per 1? Forse a causa di problemi di instabilità / thread-safe? Invece di riportare il conteggio a 8, potrebbe essere più sicuro passare a una versione thread-safe / stabile di NumPy. Forse è anche utile controllare questa variabile con l'ultimo e il più grande NumPy nel caso in cui ciò non sia più necessario, quindi tecnicamente un bug.
Andrew Atrens,


2

Guardando intontito, sembra che sotto il cofano abbia avuto problemi di attivazione / disattivazione con il multithreading e, a seconda della versione che stai utilizzando, potresti aspettarti di iniziare a vedere arresti anomali quando urti ne.set_vml_num_threads () ..

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

Ho bisogno di capire come questo è incollato all'interprete Python, dato il tuo esempio di codice in cui sembra in qualche modo consentire più chiamate apparentemente sincrone / ordinate a np.sqrt () di procedere in parallelo. Immagino che se l'interprete di Python restituisce sempre un riferimento a un oggetto quando fa scoppiare lo stack, e nel tuo esempio sta semplicemente lanciando quei riferimenti e non assegnandoli o manipolandoli in alcun modo andrebbe bene. Ma se le iterazioni di loop successive dipendono dalle precedenti, allora sembra meno chiaro come queste possano essere parallelizzate in modo sicuro. Probabilmente un fallimento silenzioso / risultati errati sono un risultato peggiore degli arresti anomali.


Ciao Andrew Atrens, ci sei quasi. È il problema di ne.set_vml_num_threads (). Grazie mille per il tuo tempo dedicato al mio problema.
user15964

Happy Trails :)
Andrew Atrens, il

0

Penso che la tua premessa iniziale possa essere errata -

Hai dichiarato: il che significa che numpy rileva automaticamente che la mia workstation ha 8 core e np.sqrt utilizza automaticamente tutti e 8 i core per accelerare il calcolo.

Una singola funzione np.sqrt () non può indovinare come verrà successivamente invocata o restituita prima che sia stata parzialmente completata. Ci sono meccanismi di parallelismo in Python, ma nessuno è automatico.

Ora, detto questo, l'interprete di Python potrebbe essere in grado di ottimizzare il ciclo for per il parallelismo, che potrebbe essere quello che stai vedendo, ma sospetto fortemente che se guardi il tempo dell'orologio a muro per l'esecuzione di questo loop sarà no diverso indipendentemente dal fatto che (apparentemente) utilizzi 8 core o 1 core.

AGGIORNAMENTO: Dopo aver letto un po 'più di commenti sembra che il comportamento multi-core che stai vedendo sia correlato alla distribuzione anaconda dell'interprete Python. Ho dato un'occhiata ma non sono riuscito a trovare alcun codice sorgente per esso, ma sembra che la licenza Python consenta alle entità (come anaconda.com) di compilare e distribuire derivati ​​dell'interprete senza richiedere la pubblicazione delle loro modifiche.

Immagino che tu possa contattare la gente dell'anaconda: il comportamento che stai vedendo sarà difficile da capire senza sapere cosa / se qualcosa è cambiato nell'interprete ..

Fai anche un rapido controllo dell'orologio a muro con / senza l'ottimizzazione per vedere se è effettivamente 8 volte più veloce - anche se hai davvero tutti gli 8 core funzionanti invece di 1 sarebbe bene sapere se i risultati sono effettivamente 8x più veloce o se ci sono spinlock in uso che stanno ancora serializzando su un singolo mutex.


1
Ciao Andrew Atrens. Ma la parallelizzazione non è fatta da Python, ma dal backend di Anaconda Numpy che è Intel MKL. E sì, ho aperto un problema su numpy, mi hanno suggerito di aprire un problema su anaconda e l'ho fatto. Tuttavia, non ho ancora ricevuto una risposta da Anaconda per una settimana. Quindi forse hanno semplicemente ignorato il mio rapporto ...
user15964 il

È un problema con la versione / versione anaconda dell'interprete python - la loro versione utilizza openmp mentre la versione standard di python no.
Andrew Atrens,

È un problema con la versione / versione anaconda dell'interprete python - la loro versione si collega a / utilizza le API openmp mentre l'interprete standard di rilascio python no. quando dico "utilizzo" intendo letteralmente chiamare le funzioni di openmp api "under the hood". Come con qualsiasi ottimizzazione implicita in cui non possiamo vedere il codice sorgente, possiamo solo segnalarlo (come hai fatto tu) e, se possibile, tentare di aggirarlo.
Andrew Atrens,

Un altro pensiero su questo .. potresti ricodificare la tua applicazione per usare esplicitamente le librerie multithreading di Python e non fare affidamento sull'ottimizzatore dell'interprete per farlo per te .. Sto pensando ai pool di thread .. a seconda di quanto sia complicata la tua applicazione, e se questo non è il tuo primo passo nella programmazione thread, questo potrebbe non essere troppo difficile .. Per mantenere la portabilità, l'uso dovrebbe probabilmente cercare di evitare qualcosa di specifico per anaconda o openmp - te lo lascio perché non ho tempo per scavare in esso ... :) In ogni caso buona fortuna e spero che questo aiuti a offuscare ciò che stai vedendo. :) :)
Andrew Atrens,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.