关于多线程的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表示numpy自动检测到我的工作站具有8个核心,并np.sqrt自动使用所有8个核心来加速计算。

但是,我发现了一个奇怪的错误。如果我运行脚本

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函数之前加上两个pandas DataFrame,则numpy的自动多线程功能将消失,而不会发出任何警告!这绝对是不合理的,为什么Pandas dataFrame计算会影响Numpy线程设置?是虫子吗?如何解决这个问题?在此处输入图片说明


PS:

我进一步使用Linux perf工具进行挖掘。

运行第一个脚本显示

在此处输入图片说明

运行第二个脚本时显示

在此处输入图片说明

因此,这两个脚本都涉及libmkl_vml_avx2.so,而第一个脚本则涉及其他libiomp5.so与openMP有关的脚本。

而且由于vml意味着intel向量数学库,所以根据vml doc,我猜至少下面的函数都是自动多线程的

在此处输入图片说明


我不确定我是否理解您的问题。你能详细说明吗?
AMC

@AMC我更新了我的帖子,希望现在已经清楚了
user15964

我认为需要更多信息,例如np,熊猫,版本,CPU,操作系统类型...我无法在计算机上复制。在两个代码中均未使用多个CPU。
hunzter

@hunzter好的,这里是信息:Ubuntu 16.04.5 LTS numpy 1.17.2 py37haad9e8e_0 pandas 0.25.1 py37he6710b0_0Intel®Xeon®CPU E5-1680 v4 @ 3.40GHz。PS。我使用anaconda
user15964 '19

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,

Answers:


13

Pandas numexpr在后台使用一些运算来计算,并numexpr导入 vml时 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等等-并且一旦numexpr设置VML线程的最大数目为1时,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,线程数,即:

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来检查此变量,因此从技术上讲是一个错误。
安德鲁·阿特伦斯


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解释器的anaconda发行版有关。我看了一下,但是找不到任何源代码,但是python许可证似乎允许实体(例如anaconda.com)编译并分发解释器的派生类,而无需发布其更改。

我猜想您可以与anaconda的人们接触-如果不知道他们在解释器中所做的更改,则很难弄清您所看到的行为。

也可以快速检查挂钟时间,选择是否进行优化,以查看是否确实快8倍-即使您真的将所有8个内核都工作了,而不是1个,也最好知道结果是否实际上是8倍速度更快,或者是否有正在使用的自旋锁仍在单个互斥锁上进行序列化。


1
嗨,安德鲁·阿特伦斯(Andrew Atrens)。但是并行化不是由python完成的,而是由anaconda numpy的后端完成的,后者是intel MKL。是的,我在numpy上打开了一个问题,他们建议我在anaconda上打开一个问题,我做到了。但是,我已经有一个星期没有收到水蟒的回音了。因此,也许他们只是忽略了我的报告...
user15964

蟒蛇解释器的anaconda版本/发行版存在问题-其版本使用openmp,而标准python发行版不使用。
Andrew Atrens

蟒蛇解释器的anaconda版本/发行版存在问题-它们的版本链接到/使用openmp api,而标准python发行版解释器则没有。当我说利用时,我的意思是“在幕后”调用openmp api函数。与任何看不见源代码的隐式优化一样,我们只能报告它(如您所愿),如果可能,请尝试解决它。
Andrew Atrens

关于此问题的另一种想法..您可以重新编码您的应用程序以显式使用python多线程库,而不必依靠解释器的优化器为您完成..我在考虑线程池..取决于您的应用程序有多复杂,如果这不是您第一次尝试线程编程,那么可能就不太困难了。为了保持可移植性,应尽量避免使用anaconda或openmp所特有的任何东西-我会把这个留给您,因为我没有时间深入探讨... :)无论如何,祝您好运,并希望这有助于消除您所看到的雾。:) :)
Andrew Atrens
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.