jpp*_*jpp 27 python performance numpy series pandas
在有条件地更新系列中的值时,我经常使用Pandas mask
和where
方法来获得更清晰的逻辑.但是,对于性能相对较高的代码,我注意到相对于性能的显着下降numpy.where
.
虽然我很乐意接受特定情况,但我很想知道:
mask
/ where
方法是否提供任何其他功能?我理解这3个参数但很少使用它们.例如,我不知道参数引用了什么. inplace
errors
try-cast
level
mask
/ where
优于numpy.where
?如果存在这样的例子,它可能会影响我选择适当方法的方式.作为参考,这里有一些关于Pandas 0.19.2/Python 3.6.0的基准测试:
np.random.seed(0)
n = 10000000
df = pd.DataFrame(np.random.random(n))
assert (df[0].mask(df[0] > 0.5, 1).values == np.where(df[0] > 0.5, 1, df[0])).all()
%timeit df[0].mask(df[0] > 0.5, 1) # 145 ms per loop
%timeit np.where(df[0] > 0.5, 1, df[0]) # 113 ms per loop
Run Code Online (Sandbox Code Playgroud)
对于非标量值,性能似乎进一步分化:
%timeit df[0].mask(df[0] > 0.5, df[0]*2) # 338 ms per loop
%timeit np.where(df[0] > 0.5, df[0]*2, df[0]) # 153 ms per loop
Run Code Online (Sandbox Code Playgroud)
ead*_*ead 22
我正在使用pandas 0.23.3和Python 3.6,所以我只能看到第二个例子的运行时间的真正差异.
但是让我们研究一下你的第二个例子的略有不同的版本(所以我们2*df[0]
放弃了).这是我的机器上的基线:
twice = df[0]*2
mask = df[0] > 0.5
%timeit np.where(mask, twice, df[0])
# 61.4 ms ± 1.51 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit df[0].mask(mask, twice)
# 143 ms ± 5.27 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
Run Code Online (Sandbox Code Playgroud)
Numpy的版本比熊猫快2.3倍.
因此,让我们分析两个函数以查看差异 - 当一个人不熟悉代码基础时,分析是一个很好的方式来获得全局:它比调试更快,并且比试图找出正在发生的事情更不容易出错只需阅读代码即可.
我在Linux上使用perf
.对于我们得到的numpy版本(列表见附录A):
>>> perf record python np_where.py
>>> perf report
Overhead Command Shared Object Symbol
68,50% python multiarray.cpython-36m-x86_64-linux-gnu.so [.] PyArray_Where
8,96% python [unknown] [k] 0xffffffff8140290c
1,57% python mtrand.cpython-36m-x86_64-linux-gnu.so [.] rk_random
Run Code Online (Sandbox Code Playgroud)
正如我们所看到的,大部分时间花费在PyArray_Where
- 约69%.未知符号是一个内核函数(事实上clear_page
) - 我没有root权限运行,因此符号未解析.
对于大熊猫我们得到(参见附录B代码):
>>> perf record python pd_mask.py
>>> perf report
Overhead Command Shared Object Symbol
37,12% python interpreter.cpython-36m-x86_64-linux-gnu.so [.] vm_engine_iter_task
23,36% python libc-2.23.so [.] __memmove_ssse3_back
19,78% python [unknown] [k] 0xffffffff8140290c
3,32% python umath.cpython-36m-x86_64-linux-gnu.so [.] DOUBLE_isnan
1,48% python umath.cpython-36m-x86_64-linux-gnu.so [.] BOOL_logical_not
Run Code Online (Sandbox Code Playgroud)
相当不同的情况:
PyArray_Where
在引擎盖下使用- 最突出的时间消费者是vm_engine_iter_task
,这是一种数字功能.__memmove_ssse3_back
使用大约25
的时间!可能一些内核的功能也连接到内存访问.实际上,PyArray_Where
在引擎盖下使用pandas-0.19 ,对于旧版本的perf报告看起来像:
Overhead Command Shared Object Symbol
32,42% python multiarray.so [.] PyArray_Where
30,25% python libc-2.23.so [.] __memmove_ssse3_back
21,31% python [kernel.kallsyms] [k] clear_page
1,72% python [kernel.kallsyms] [k] __schedule
Run Code Online (Sandbox Code Playgroud)
所以基本上它会np.where
在引擎盖下使用+一些开销(所有上面的数据复制,请参阅__memmove_ssse3_back
).
在Pandas的0.19版本中,我看不到大熊猫变得比numpy更快的情况 - 它只是增加了numpy功能的开销.Pandas的版本0.23.3是一个完全不同的故事 - 这里使用了numexpr-module,很可能有一些情况下pandas的版本(至少稍微)更快.
我不确定这个内存复制是否真的被称为/必要 - 也许甚至可以称之为性能错误,但我只是不知道还不确定.
我们可以通过剥离一些间接(np.array
而不是通过pd.Series
)来帮助大熊猫不要复制.例如:
%timeit df[0].mask(mask.values > 0.5, twice.values)
# 75.7 ms ± 1.5 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
Run Code Online (Sandbox Code Playgroud)
现在,大熊猫只慢了25%.perf说:
Overhead Command Shared Object Symbol
50,81% python interpreter.cpython-36m-x86_64-linux-gnu.so [.] vm_engine_iter_task
14,12% python [unknown] [k] 0xffffffff8140290c
9,93% python libc-2.23.so [.] __memmove_ssse3_back
4,61% python umath.cpython-36m-x86_64-linux-gnu.so [.] DOUBLE_isnan
2,01% python umath.cpython-36m-x86_64-linux-gnu.so [.] BOOL_logical_not
Run Code Online (Sandbox Code Playgroud)
更少的数据复制,但仍然比numpy的版本更多,这是负担开销的主要原因.
我的主要内容是:
大熊猫有可能至少比numpy快一点(因为它可能更快).然而,大熊猫对数据复制的处理有些不透明,这使得很难预测这种可能性何时被(不必要的)数据复制所掩盖.
当where
/ 的性能mask
是瓶颈的时候,我会用numba/cython来提高性能 - 看看我在下面进一步尝试使用numba和cython.
我的想法是采取
np.where(df[0] > 0.5, df[0]*2, df[0])
Run Code Online (Sandbox Code Playgroud)
版本并消除了创建临时的需要 - 即df[0]*2
.
正如@ max9111所提出的,使用numba:
import numba as nb
@nb.njit
def nb_where(df):
n = len(df)
output = np.empty(n, dtype=np.float64)
for i in range(n):
if df[i]>0.5:
output[i] = 2.0*df[i]
else:
output[i] = df[i]
return output
assert(np.where(df[0] > 0.5, twice, df[0])==nb_where(df[0].values)).all()
%timeit np.where(df[0] > 0.5, df[0]*2, df[0])
# 85.1 ms ± 1.61 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit nb_where(df[0].values)
# 17.4 ms ± 673 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
Run Code Online (Sandbox Code Playgroud)
这比numpy的版本快5倍!
这是我在Cython的帮助下尝试提高性能的成功:
%%cython -a
cimport numpy as np
import numpy as np
cimport cython
@cython.boundscheck(False)
@cython.wraparound(False)
def cy_where(double[::1] df):
cdef int i
cdef int n = len(df)
cdef np.ndarray[np.float64_t] output = np.empty(n, dtype=np.float64)
for i in range(n):
if df[i]>0.5:
output[i] = 2.0*df[i]
else:
output[i] = df[i]
return output
assert (df[0].mask(df[0] > 0.5, 2*df[0]).values == cy_where(df[0].values)).all()
%timeit cy_where(df[0].values)
# 66.7± 753 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
Run Code Online (Sandbox Code Playgroud)
加速25%.不确定,为什么cython比numba慢得多.
人数:
答: np_where.py:
import pandas as pd
import numpy as np
np.random.seed(0)
n = 10000000
df = pd.DataFrame(np.random.random(n))
twice = df[0]*2
for _ in range(50):
np.where(df[0] > 0.5, twice, df[0])
Run Code Online (Sandbox Code Playgroud)
B: pd_mask.py:
import pandas as pd
import numpy as np
np.random.seed(0)
n = 10000000
df = pd.DataFrame(np.random.random(n))
twice = df[0]*2
mask = df[0] > 0.5
for _ in range(50):
df[0].mask(mask, twice)
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
1440 次 |
最近记录: |