Dziwny błąd w Pandach i Numpy dotyczący wielowątkowości

25

Większość funkcji Numpy domyślnie włącza wielowątkowość.

na przykład pracuję na 8-rdzeniowej stacji roboczej z procesorem Intel, jeśli uruchomię skrypt

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

Linux topwyświetli 800% użycia procesora podczas pracy, wprowadź opis zdjęcia tutaj co oznacza, że ​​numpy automatycznie wykrywa, że ​​moja stacja robocza ma 8 rdzeni i np.sqrtautomatycznie używa wszystkich 8 rdzeni, aby przyspieszyć obliczenia.

Znalazłem jednak dziwny błąd. Jeśli uruchomię skrypt

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)

użycie procesora wynosi 100% !!. Oznacza to, że jeśli dodasz dwie pandy DataFrame przed uruchomieniem dowolnej funkcji numpy, funkcja automatycznego wielowątkowości numpy zniknie bez żadnego ostrzeżenia! To absolutnie nieuzasadnione, dlaczego obliczenia Pandas dataFrame wpłynęłyby na ustawienie wątków Numpy? Czy to błąd? Jak obejść ten problem?wprowadź opis zdjęcia tutaj


PS:

Kopie dalej za pomocą perfnarzędzia Linux .

uruchamianie pierwszych pokazów skryptów

wprowadź opis zdjęcia tutaj

Podczas uruchamiania drugiego pokazuje skrypt

wprowadź opis zdjęcia tutaj

Zatem oba skrypty obejmują libmkl_vml_avx2.so, podczas gdy pierwszy skrypt obejmuje dodatkowe, libiomp5.soktóre wydają się być powiązane z openMP.

A ponieważ vml oznacza bibliotekę wektorów matematycznych Intel, więc zgodnie z vml doc myślę, że przynajmniej poniższe funkcje są automatycznie wielowątkowe

wprowadź opis zdjęcia tutaj

użytkownik15964
źródło
Nie jestem pewien, czy rozumiem twoje pytanie. Czy możesz rozwinąć?
AMC,
@AMC Zaktualizowałem swój post, mam nadzieję, że jest teraz jasny
user15964
Myślę, że potrzeba więcej informacji, takich jak np. Pandy, wersja, procesor, typ systemu operacyjnego ... Nie mogę odtworzyć na moim komputerze. Nie wykorzystuje wielu procesorów w obu kodach.
hunzter
@ Hunzter OK, oto informacje: Ubuntu 16.04.5 LTS numpy 1.17.2 py37haad9e8e_0 pandy 0.25.1 py37he6710b0_0 Procesor Intel (R) Xeon (R) E5-1680 v4 @ 3.40GHz. PS. Używam anakondy
user15964,
1
Czy możesz to sprawdzić: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,

Odpowiedzi:

13

Panda używa numexprpod maską do obliczania niektórych operacji i numexprustawia maksymalną liczbę wątków dla vml na 1, gdy jest importowany :

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

i jest importowany przez pandy, gdy df+dfjest oceniany w expressions.py :

from pandas.core.computation.check import _NUMEXPR_INSTALLED

if _NUMEXPR_INSTALLED:
   import numexpr as ne

Jednak dystrybucja Anaconda wykorzystuje również VML-funkcjonalność takich funkcji jak sqrt, sin, cosi tak dalej - i raz numexprustawić maksymalna liczba wątków do VML-1, NumPy-funkcjonuje już używać zrównoleglanie.

Problem można łatwo zauważyć w gdb (używając powolnego skryptu):

>>> 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

tzn. widzimy, numexprustawia liczbę wątków na 1. Co jest później używane, gdy wywoływana jest funkcja 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

Widzimy więc, że numpy używa implementacji vml, z vdSqrtktórej korzysta, mkl_vml_serv_threader_d_1i_1oaby zdecydować, czy obliczenia powinny być wykonywane równolegle i wygląda na liczbę wątków:

(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

rejestr %raxma maksymalną liczbę wątków i wynosi 1.

Teraz możemy użyć numexprdo zwiększenia liczby wątków vml , tj .:

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

Teraz wykorzystuje się wiele rdzeni!

ead
źródło
Dziękuję bardzo! Wreszcie świetna odpowiedź wyjaśnia wszystko. Ostatecznie jest to numexprza kulisami.
user15964
Uzgodnione .. dobre kopanie! Następne pytanie… dlaczego wątek wypychający numexpr liczy się do 1? Być może z powodu problemów z niestabilnością / bezpieczeństwem wątków? Zamiast zwiększać liczbę z powrotem do 8, bezpieczniej jest przejść do bezpiecznej / stabilnej wersji NumPy. Może warto również sprawdzić tę zmienną za pomocą najnowszego i największego NumPy na wypadek, gdyby tak naprawdę nie była już potrzebna, a więc technicznie błąd.
Andrew Atrens
2

Patrząc na numpy, wygląda na to, że pod maską miał problemy z włączaniem / wyłączaniem wielowątkowości, i w zależności od używanej wersji, możesz spodziewać się awarii, gdy podbijesz ne.set_vml_num_threads () ..

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

Muszę się zastanowić, jak to jest przyklejone do interpretera python, biorąc pod uwagę twój przykład kodu, w którym wydaje się, że w jakiś sposób pozwala na wiele pozornie synchronicznych / uporządkowanych wywołań do np.sqrt () równolegle. Wydaje mi się, że jeśli interpreter Pythona zawsze zwraca referencję do obiektu, gdy wyskakuje on na stosie, a w twoim przykładzie jest po prostu przechwytywanie tych referencji, a nie przypisywanie lub manipulowanie nimi w jakikolwiek sposób, byłoby dobrze. Ale jeśli kolejne iteracje pętli zależą od poprzednich, wydaje się mniej jasne, jak można je bezpiecznie zrównoleglić. Prawdopodobnie cicha awaria / błędne wyniki są gorsze niż awarie.

Andrew Atrens
źródło
Cześć Andrew Atrens, Już prawie jesteś. Jest to problem ne.set_vml_num_threads (). Dziękuję bardzo za poświęcony czas na moją sprawę.
user15964
Happy Trails :)
Andrew Atrens,
0

Myślę, że twoje początkowe założenie może być nieprawidłowe -

Stwierdziłeś: co oznacza, że ​​numpy automatycznie wykrywa, że ​​moja stacja robocza ma 8 rdzeni, a np.sqrt automatycznie używa wszystkich 8 rdzeni w celu przyspieszenia obliczeń.

Pojedyncza funkcja np.sqrt () nie może odgadnąć, w jaki sposób zostanie ona później wywołana lub zwrócona, zanim zostanie częściowo zakończona. W Pythonie istnieją mechanizmy równoległości, ale żaden nie jest automatyczny.

Teraz, powiedziawszy to, interpreter pythona może być w stanie zoptymalizować pętlę for pod kątem równoległości, co może być tym, co widzisz, ale mocno podejrzewam, że jeśli spojrzysz na czas zegara na wykonanie tej pętli, nie będzie różni się niezależnie od tego, czy (najwyraźniej) używasz 8 rdzeni czy 1 rdzeń.

AKTUALIZACJA: Po przeczytaniu nieco więcej komentarzy wydaje się, że obserwowane zachowanie wielordzeniowe jest związane z rozkładem anakondy interpretera python. Przyjrzałem się, ale nie byłem w stanie znaleźć dla niego żadnego kodu źródłowego, ale wygląda na to, że licencja python pozwala podmiotom (takim jak anaconda.com) na kompilowanie i dystrybucję pochodnych interpretera bez konieczności publikowania ich zmian.

Wydaje mi się, że możesz dotrzeć do ludzi anakondy - zachowanie, które widzisz, będzie trudne do zrozumienia, nie wiedząc, co / jeśli coś zmienili w tłumaczu.

Wykonaj również szybką kontrolę czasu zegara ściennego z / bez optymalizacji, aby sprawdzić, czy rzeczywiście jest on 8-krotnie szybszy - nawet jeśli naprawdę masz wszystkie 8 rdzeni zamiast 1, dobrze byłoby wiedzieć, czy wyniki są rzeczywiście 8-krotnie szybciej lub jeśli w użyciu są blokady, które wciąż serializują się na jednym muteksie.

Andrew Atrens
źródło
1
Cześć Andrew Atrens. Ale paralelizacja nie jest wykonywana przez python, lecz przez backend anakondy numpy, który jest intel MKL. I tak, otworzyłem numer na numpy, zasugerowali mi, żebym otworzył numer dotyczący anakondy i zrobiłem to. Jednak przez tydzień nie otrzymałem ani jednej odpowiedzi od anakondy. Więc może po prostu zignorowali mój raport ...
user15964
Jest to problem z wersją / wydaniem anakondy interpretera python - ich wersja wykorzystuje openmp, podczas gdy standardowe wydanie python nie.
Andrew Atrens,
Jest to problem z wersją / wydaniem anakondy interpretera Pythona - ich wersja łączy się z / używa openmp apis, podczas gdy standardowy interpreter wydania Pythona nie. kiedy mówię, że używa, mam na myśli dosłownie wywoływanie funkcji openmp API „pod maską”. Jak w przypadku każdej domyślnej optymalizacji, w której nie widzimy kodu źródłowego, możemy go tylko zgłosić (tak jak Ty) i, jeśli to możliwe, spróbować go obejść.
Andrew Atrens
Kolejna myśl na ten temat ... możesz ponownie zakodować aplikację, aby jawnie korzystać z bibliotek wielowątkowych w Pythonie i nie polegać na optymalizatorze interpretera, który to zrobi za Ciebie .. Myślę o pulach wątków .. w zależności od tego, jak skomplikowana jest Twoja aplikacja, a jeśli nie jest to twoje pierwsze podejście do programowania wątkowego, może to nie być zbyt trudne. Aby zachować przenośność, prawdopodobnie powinieneś spróbować uniknąć czegoś specyficznego dla anakondy lub openmp - zostawię to tobie, ponieważ nie mam czasu kopać w to ... :) W każdym razie powodzenia i mam nadzieję, że pomoże to zamglić to, co widzisz. :) :)
Andrew Atrens