マルチスレッドに関するPandasとNumpyの奇妙なバグ


25

Numpyの関数のほとんどは、デフォルトでマルチスレッドを有効にします。

たとえば、スクリプトを実行すると、8コアのIntel CPUワークステーションで作業します

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

Linux topは、実行中に800%のCPU使用率を表示し ここに画像の説明を入力してください ます。つまり、numpyはワークステーションに8つのコアがあることを自動的に検出し、8つのコアnp.sqrtすべてを自動的に使用して計算を高速化します。

しかし、私は奇妙なバグを見つけました。スクリプトを実行すると

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)

CPU使用率は100%です!!。 つまり、numpy関数を実行する前に2つのpandas DataFrameを追加すると、numpyの自動マルチスレッド機能は警告なしに消えてしまいます。これは絶対に合理的ではありません、なぜPandas dataFrame計算はNumpyスレッド設定に影響するのですか?バグですか?これを回避する方法は?ここに画像の説明を入力してください


PS:

Linux perfツールを使用してさらに掘り下げます。

最初のスクリプトショーの実行

ここに画像の説明を入力してください

2番目のスクリプトの実行中に

ここに画像の説明を入力してください

したがって、両方のスクリプトにはが含まれますがlibmkl_vml_avx2.so、最初のスクリプトにはlibiomp5.soopenMPに関連すると思われる追加のスクリプトが含まれます。

そして、vmlはIntelベクトル数学ライブラリを意味するので、vml docによれば、少なくとも以下の関数はすべて自動的にマルチスレッド化されると思います

ここに画像の説明を入力してください


私はあなたの質問を理解しているのかわかりません。詳しく説明できますか?
AMC、

@AMC私は私の投稿を更新しました、それが明確になったことを願っています
user15964

np、pandas、バージョン、CPU、OSタイプなどの詳細情報が必要だと思います...私のマシンでは再現できません。両方のコードで複数のCPUを利用していません。
ハンター、

@hunzter OK、こちらが情報です。Ubuntu16.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。私はanacondaを使用しています
user15964

1
これを確認してください: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

回答:


13

Pandasは内部でnumexprいくつかの操作を計算するために使用し、インポートnumexpr時にvmlの最大スレッド数を1に設定します。

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

またdf+dfexpressions.pyで評価されると、パンダによってインポートされます。

from pandas.core.computation.check import _NUMEXPR_INSTALLED

if _NUMEXPR_INSTALLED:
   import numexpr as ne

しかし、アナコンダ分布はまた、そのような機能のためにVML-機能を使用してsqrtsincosなど-と、一度numexpr1にVML-スレッドの最大数を設定し、numpyの関数もはや使用並列。

問題はgdbで簡単に確認できます(遅いスクリプトを使用)。

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

つまりnumexpr、スレッドの数を1に設定して確認できます。これは、後で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

したがって、numpyがvmlの実装vdSqrtを使用してmkl_vml_serv_threader_d_1i_1o、計算を並列に実行する必要があるかどうかを判断し、スレッドの数を調べることがわかります。

(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

レジスタに%raxはスレッドの最大数があり、それは1です。

これで、を使用numexprしてvml-threadsの数増やすことができます。つまり、

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

現在、複数のコアが利用されています!


どうもありがとうございます!最後に、すばらしい答えがすべてを説明します。結局、それはnumexpr舞台裏です。
user15964

同意しました。次の質問ですが、なぜnumexprはスレッド数を1にプッシュするのですか?おそらく不安定性/スレッドセーフの問題が原因ですか?カウントを8に戻す代わりに、NumPyのスレッドセーフ/安定バージョンに移行する方が安全な場合があります。これが実際に必要なくなった場合に備えて、最新かつ最大のNumPyでこの変数をチェックすることも良いでしょう。したがって、技術的にはバグです。
Andrew Atrens

@AndrewAtrensは、github.com / pydata
numexpr

2

numpyを見ると、内部的にはマルチスレッドのオン/オフの問題があり、使用しているバージョンによっては、ne.set_vml_num_threads()をバンプするとクラッシュが発生することが予想される場合があります。

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

np.sqrt()への複数の明らかに同期/順序付けされた呼び出しを並列処理できるように見えるコードの例を考えると、これがどのようにpythonインタープリターに接着されているかを理解する必要があります。スタックをポップするときに、Pythonインタープリターが常にオブジェクトへの参照を返すだけで、あなたの例ではそれらの参照をピッチしているだけで、割り当てや操作を行っていないのではないかと思います。しかし、その後のループの繰り返しが以前のループの繰り返しに依存している場合、これらを安全に並列化する方法が不明確になります。間違いなくサイレント障害/間違った結果は、クラッシュよりも悪い結果です。


こんにちは、Andrew Atrens、あなたはもうすぐです。それはne.set_vml_num_threads()の問題です。私の問題についてのあなたの専用の時間をどうもありがとうございました。
user15964

ハッピートレイル:)
アンドリューアトレン

0

私はあなたの最初の前提が間違っていると思います-

あなたは述べました:これは、numpyがワークステーションに8つのコアがあることを自動的に検出し、np.sqrtが自動的に8つのコアすべてを使用して計算を高速化することを意味します。

単一の関数np.sqrt()は、部分的に完了する前に、次に呼び出される方法または戻る方法を推測できません。Pythonには並列処理メカニズムがありますが、自動化されるメカニズムはありません。

さて、Pythonインタープリターはforループを並列化のために最適化できるかもしれませんが、これはあなたが見ているものかもしれませんが、あなたがこのループを実行するための実時間を見れば、それは実行されないでしょう(見たところ)8コアと1コアのどちらを使用しているかに関係なく異なります。

更新:コメントをもう少し読んだら、マルチコアの動作がPythonインタープリターのアナコンダディストリビューションに関連しているようです。調べましたが、そのソースコードは見つかりませんでしたが、pythonライセンスにより、エンティティ(anaconda.comなど)がインタープリターの派生物をコンパイルして配布し、変更を公開する必要がないようです。

あなたはアナコンダの人々に手を差し伸べることができると思います-あなたが見ている行動は、彼らがインタプリタで何が/何か変更されたかを知らなければ、理解するのが難しいでしょう...

また、最適化の有無にかかわらず、実時間のクイックチェックを実行して、実際に8倍高速であるかどうかを確認します。実際に1コアではなく8コアすべてが機能している場合でも、結果が実際に8倍であるかどうかを確認するとよいでしょう。より高速な場合、または単一のミューテックスでまだシリアル化されている使用中のスピンロックがある場合。


1
こんにちは、Andrew Atrens。しかし、並列化はPythonでは行われず、Intel MKLであるanaconda numpyのバックエンドで行われます。そして、はい、私はnumpyで問題を開きました、彼らはアナコンダで問題を開くことを提案しました、そして私はそれをしました。しかし、私はまだアナコンダから1週間も返事がありませんでした。だから多分彼らは私のレポートを無視しました...
user15964

これはpythonインタープリターのanacondaバージョン/リリースの問題です-それらのバージョンはopenmpを利用していますが、標準のpythonリリースはそうではありません。
Andrew Atrens

これは、pythonインタープリターのanacondaバージョン/リリースの問題です。それらのバージョンは/にリンクしていて、標準のpythonリリースインタープリターはopenmp apiを利用していません。私が利用していると言うとき、私は文字通り「内部」でopenmp api関数を呼び出すことを意味します。ソースコードが見えない暗黙の最適化と同様に、それは(あなたが持っているように)報告することしかできず、可能であれば回避策を​​試みることができます。
Andrew Atrens

これについての別の考え..あなたが明示的にPythonマルチスレッドライブラリを使用するようにアプリケーションを再コード化し、インタプリタのオプティマイザに依存することなくそれを行うことができます..私はアプリケーションの複雑さに応じてスレッドプールを考えています...これがスレッドプログラミングの初心者でない場合、これはそれほど難しいことではないかもしれません。移植性を維持するには、おそらくアナコンダまたはopenmpに固有の何かを避けようとする必要があります-時間がないので、これはあなたにお任せしますそれを掘り下げるには... :)とにかく最高の幸運とこれがあなたが見ているものを曇らせるのに役立つことを願っています。:) :)
Andrew Atrens
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.