Seltsamer Fehler in Pandas und Numpy in Bezug auf Multithreading

25

Die meisten Funktionen des Numpy aktivieren standardmäßig Multithreading.

Ich arbeite beispielsweise auf einer Intel-CPU-Workstation mit 8 Kernen, wenn ich ein Skript ausführe

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

Das Linux topzeigt 800% CPU-Auslastung während des Betriebs an, Geben Sie hier die Bildbeschreibung ein was bedeutet, dass numpy automatisch erkennt, dass meine Workstation 8 Kerne hat, und np.sqrtautomatisch alle 8 Kerne verwendet, um die Berechnung zu beschleunigen.

Ich habe jedoch einen seltsamen Fehler gefunden. Wenn ich ein Skript ausführe

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)

Die CPU-Auslastung beträgt 100% !!. Dies bedeutet, dass die automatische Multithreading-Funktion von numpy ohne Vorwarnung wegfällt, wenn Sie vor dem Ausführen einer Numpy-Funktion zwei Pandas DataFrame plus verwenden! Dies ist absolut nicht sinnvoll. Warum wirkt sich die Berechnung von Pandas dataFrame auf die Numpy-Threading-Einstellung aus? Ist es ein Fehler? Wie kann man das umgehen?Geben Sie hier die Bildbeschreibung ein


PS:

Ich grabe weiter mit Linux- perfTool.

Das erste Skript wird angezeigt

Geben Sie hier die Bildbeschreibung ein

Beim Ausführen zeigt das zweite Skript

Geben Sie hier die Bildbeschreibung ein

Beide Skripte beinhalten also libmkl_vml_avx2.so, während das erste Skript zusätzliche beinhaltet libiomp5.so, die mit openMP in Zusammenhang zu stehen scheinen.

Und da vml Intel Vector Math Library bedeutet, werden laut vml doc zumindest die folgenden Funktionen automatisch alle multithreaded

Geben Sie hier die Bildbeschreibung ein

user15964
quelle
Ich bin mir nicht sicher, ob ich deine Frage verstehe. Können Sie das näher erläutern?
AMC
@AMC Ich habe meinen Beitrag aktualisiert, hoffe es ist jetzt klar
user15964
Ich denke, es werden mehr Informationen wie np, pandas, version, CPU, OS-Typ ... benötigt. Ich kann nicht auf meinem Computer reproduzieren. In beiden Codes werden nicht mehrere CPUs verwendet.
Hunzter
@hunzter OK, hier sind Informationen: Ubuntu 16.04.5 LTS numpy 1.17.2 py37haad9e8e_0 pandas 0.25.1 py37he6710b0_0 Intel (R) Xeon (R) CPU E5-1680 v4 @ 3.40GHz. PS. Ich benutze
anaconda
1
Könnten Sie dies bitte überprüfen: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

Antworten:

13

Pandas Anwendungen numexprunter der Haube einige Operationen zu berechnen, und numexprstellt die maximale Anzahl von Threads für VML auf 1, wenn es eingeführt wird :

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

und es wird von Pandas importiert, wenn df+dfes in expression.py ausgewertet wird :

from pandas.core.computation.check import _NUMEXPR_INSTALLED

if _NUMEXPR_INSTALLED:
   import numexpr as ne

Jedoch auch Anaconda Verteilung verwendet VML-Funktionalität für solche Funktionen wie sqrt, sin, cosund so weiter - und wenn numexprdie maximale Anzahl von VML-Fäden 1, die numpy-Funktionen nicht länger Verwendung Parallelisierung eingestellt.

Das Problem kann leicht in gdb gesehen werden (mit Ihrem langsamen Skript):

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

dh wir können sehen, numexprsetzt die Anzahl der Threads auf 1. Was später verwendet wird, wenn die vml-sqrt-Funktion aufgerufen wird:

(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

So wir numpy Anwendungen VML-Implementierung sehen vdSqrtwelcher verwendet , mkl_vml_serv_threader_d_1i_1oum zu entscheiden , ob die Berechnung parallel durchgeführt werden sollte , und es sieht die Anzahl der Threads:

(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

Das Register %raxhat die maximale Anzahl von Threads und ist 1.

Jetzt können wir nutzen , numexprum die Anzahl der VML-Threads zu erhöhen , das heißt:

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

Jetzt werden mehrere Kerne verwendet!

ead
quelle
Ich danke dir sehr! Schließlich erklärt eine gute Antwort alles. Am Ende ist es der numexprBlick hinter die Kulissen.
user15964
Einverstanden .. gut graben! Nächste Frage aber .. warum drückt numexpr den Thread auf 1? Möglicherweise aufgrund von Instabilitäts- / Thread-sicheren Problemen? Anstatt die Anzahl wieder auf 8 zu erhöhen, ist es möglicherweise sicherer, zu einer thread-sicheren / stabilen Version von NumPy zu wechseln. Vielleicht ist es auch gut, diese Variable mit dem neuesten und besten NumPy zu überprüfen, falls dies tatsächlich nicht mehr benötigt wird, daher technisch gesehen ein Fehler.
Andrew Atrens
@ AndrewAtrens können Sie einen Blick auf github.com/pydata/numexpr/issues/39 und github.com/pydata/numexpr/issues/355
werfen
2

Wenn man sich numpy ansieht, sieht es so aus, als ob unter der Haube Probleme mit Multithreading aufgetreten sind. Je nachdem, welche Version Sie verwenden, kann es zu Abstürzen kommen, wenn Sie auf ne.set_vml_num_threads () stoßen.

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

Ich muss mir überlegen, wie dies in den Python-Interpreter eingeklebt wird, wenn man Ihr Codebeispiel betrachtet, bei dem es irgendwie möglich zu sein scheint, dass mehrere scheinbar synchrone / geordnete Aufrufe von np.sqrt () parallel ablaufen. Ich denke, wenn der Python-Interpreter immer nur eine Referenz auf ein Objekt zurückgibt, wenn es den Stapel öffnet, und in Ihrem Beispiel nur diese Referenzen aufwirft und sie nicht in irgendeiner Weise zuweist oder manipuliert, wäre das in Ordnung. Wenn nachfolgende Schleifeniterationen jedoch von vorherigen abhängen, scheint es weniger klar zu sein, wie diese sicher parallelisiert werden können. Wohl stilles Versagen / falsche Ergebnisse sind ein Ergebnis, das schlimmer ist als Abstürze.

Andrew Atrens
quelle
Hallo Andrew Atrens, du bist fast da. Es ist das Problem von ne.set_vml_num_threads (). Vielen Dank für Ihre engagierte Zeit zu meinem Thema.
user15964
Happy Trails :)
Andrew Atrens
0

Ich denke, dass Ihre ursprüngliche Prämisse möglicherweise falsch ist -

Sie haben angegeben: Das bedeutet, dass numpy automatisch erkennt, dass meine Workstation 8 Kerne hat, und np.sqrt automatisch alle 8 Kerne verwendet, um die Berechnung zu beschleunigen.

Eine einzelne Funktion np.sqrt () kann nicht erraten, wie sie als nächstes aufgerufen oder zurückgegeben wird, bevor sie teilweise abgeschlossen ist. Es gibt Parallelitätsmechanismen in Python, aber keine sind automatisch.

Nun, obwohl der Python-Interpreter in der Lage sein könnte, die for-Schleife für Parallelität zu optimieren, was Sie vielleicht sehen, aber ich vermute stark, wenn Sie sich die Wanduhrzeit ansehen, für die diese Schleife ausgeführt wird, wird es nein sein unterschiedlich, unabhängig davon, ob Sie (anscheinend) 8 Kerne oder 1 Kern verwenden.

UPDATE: Nachdem Sie ein bisschen mehr von den Kommentaren gelesen haben, scheint es, als ob das Multi-Core-Verhalten, das Sie sehen, mit der Anakonda-Verteilung des Python-Interpreters zusammenhängt. Ich habe einen Blick darauf geworfen, konnte aber keinen Quellcode dafür finden, aber es scheint, dass die Python-Lizenz Entitäten (wie anaconda.com) erlaubt, Derivate des Interpreters zu kompilieren und zu verteilen, ohne dass deren Änderungen veröffentlicht werden müssen.

Ich denke, Sie können sich an die Anaconda-Leute wenden - das Verhalten, das Sie sehen, wird schwer herauszufinden sein, ohne zu wissen, was / ob sich etwas am Dolmetscher geändert hat.

Überprüfen Sie auch schnell die Wanduhrzeit mit / ohne Optimierung, um festzustellen, ob sie tatsächlich 8x schneller ist - selbst wenn wirklich alle 8 Kerne anstelle von 1 funktionieren, ist es gut zu wissen, ob die Ergebnisse tatsächlich 8x sind schneller oder wenn Spinlocks verwendet werden, die noch auf einem einzelnen Mutex serialisiert werden.

Andrew Atrens
quelle
1
Hallo Andrew Atrens. Die Parallelisierung erfolgt jedoch nicht über Python, sondern über das Backend von Anaconda Numpy, das Intel MKL ist. Und ja, ich habe eine Ausgabe über Numpy eröffnet. Sie haben mir vorgeschlagen, eine Ausgabe über Anakonda zu eröffnen, und ich habe es getan. Allerdings habe ich eine Woche lang noch keine einzige Antwort von anaconda erhalten. Vielleicht haben sie meinen Bericht einfach ignoriert ...
user15964
Es ist ein Problem mit der Anaconda-Version / Version des Python-Interpreters - ihre Version verwendet openmp, während die Standard-Python-Version dies nicht tut.
Andrew Atrens
Es ist ein Problem mit der Anaconda-Version / Version des Python-Interpreters - ihre Version ist mit openmp apis verknüpft / verwendet diese, während dies beim Standard-Python-Release-Interpreter nicht der Fall ist. Wenn ich "utilates" sage, meine ich wörtlich "openmp api functions" "unter der Haube". Wie bei jeder impliziten Optimierung, bei der der Quellcode nicht angezeigt wird, können wir ihn nur (wie Sie) melden und wenn möglich versuchen, ihn zu umgehen.
Andrew Atrens
Ein weiterer Gedanke dazu: Sie könnten Ihre Anwendung neu codieren, um explizit Python-Multithreading-Bibliotheken zu verwenden, und sich nicht auf den Optimierer des Interpreters verlassen, um dies für Sie zu tun. Ich denke an Thread-Pools. Je nachdem, wie kompliziert Ihre Anwendung ist. und wenn dies nicht Ihr erster Einstieg in die Thread-Programmierung ist, ist dies möglicherweise nicht allzu schwierig. Um die Portabilität aufrechtzuerhalten, sollten Sie wahrscheinlich versuchen, alles zu vermeiden, was für Anaconda oder OpenMP spezifisch ist. Ich überlasse dies Ihnen, da ich keine Zeit habe um darin zu graben ... :) Wie auch immer, viel Glück und hoffe, dass dies hilft, das, was Sie sehen, zu entnebeln. :) :)
Andrew Atrens