Numpy `matmul` 在数组视图上的性能比 `dot` 差约 100 倍

Sim*_*ksy 5 python performance numpy

我注意到matmulnumpy 中的函数的性能比乘以数组视图时的函数要差得多dot。在这种情况下,我的数组视图是复杂数组的真实部分。这是重现该问题的一些代码:

import numpy as np
from timeit import timeit
N = 1300
xx = np.random.randn(N, N) + 1j
yy = np.random.randn(N, N) + 1J

x = np.real(xx)
y = np.real(yy)
assert np.shares_memory(x, xx)
assert np.shares_memory(y, yy)

dot = timeit('np.dot(x,y)', number = 10, globals = globals())
matmul = timeit('np.matmul(x,y)', number = 10, globals = globals())

print('time for np.matmul: ', matmul)
print('time for np.dot: ', dot)
Run Code Online (Sandbox Code Playgroud)

在我的机器上输出如下:

time for np.matmul:  23.023062199994456
time for np.dot:  0.2706864000065252
Run Code Online (Sandbox Code Playgroud)

这显然与共享内存有关,因为替换np.real(xx)np.real(xx).copy()可以消除性能差异。

浏览 numpy 文档并不是特别有帮助,因为列出的差异没有讨论处理内存视图时的实现细节。

hpa*_*ulj 1

这些时间表明dot正在执行copy以下操作real

\n
In [22]: timeit np.dot(xx.real,xx.real)\n232 ms \xc2\xb1 3.34 ms per loop (mean \xc2\xb1 std. dev. of 7 runs, 1 loop each)\n\nIn [23]: timeit np.dot(xx.real.copy(),xx.real.copy())\n232 ms \xc2\xb1 4.18 ms per loop (mean \xc2\xb1 std. dev. of 7 runs, 1 loop each)\n
Run Code Online (Sandbox Code Playgroud)\n

将其应用于matmul几乎相同的时间:

\n
In [24]: timeit np.matmul(xx.real.copy(),xx.real.copy())\n231 ms \xc2\xb1 3.54 ms per loop (mean \xc2\xb1 std. dev. of 7 runs, 1 loop each)\n
Run Code Online (Sandbox Code Playgroud)\n

再次,matmul我们real正在走一些缓慢的路线。 matmul/dot当给定数组时,两者都表现较差int,尽管没有实际情况那么慢matmul realmatmul/dot也可以处理object数据类型,但速度更慢。

\n

因此,作为 python 级别的用户,有很多幕后发生的事情是我们看不到的(也没有记录在案)。

\n

编辑

\n

我很想改变标题以关注复实数,但决定检查另一个view- 浮点数组的切片

\n
In [42]: y=xx.real.copy()[::2,::2];y.shape,y.dtype\nOut[42]: ((650, 650), dtype('float64'))\n\nIn [43]: timeit np.dot(y,y)\n36.4 ms \xc2\xb1 63.4 \xc2\xb5s per loop (mean \xc2\xb1 std. dev. of 7 runs, 10 loops each)\nIn [44]: timeit np.dot(y.copy(),y.copy())\n35.6 ms \xc2\xb1 191 \xc2\xb5s per loop (mean \xc2\xb1 std. dev. of 7 runs, 10 loops each)\n
Run Code Online (Sandbox Code Playgroud)\n

再次明显的是,dot正在使用copies视图。 matmul才不是:

\n
In [45]: timeit np.matmul(y,y)\n1.89 s \xc2\xb1 3.01 ms per loop (mean \xc2\xb1 std. dev. of 7 runs, 1 loop each)\n
Run Code Online (Sandbox Code Playgroud)\n

但对于副本,时间与点相同:

\n
In [46]: timeit np.matmul(y.copy(),y.copy())\n35.3 ms \xc2\xb1 102 \xc2\xb5s per loop (mean \xc2\xb1 std. dev. of 7 runs, 10 loops each)\n
Run Code Online (Sandbox Code Playgroud)\n

所以我的猜测是,如果不能将数组直接发送到 BLAS 例程,则 dot通常会生成 a 。显然采取了一条较慢的路线。copymatmul

\n

编辑

\n

虽然它们对 2d 数组的处理类似,dotmatmul在处理 3+d 数组的方式上却有很大不同。事实上,添加的主要原因@是为矩阵乘法提供方便的“批量”概念。

\n

坚持使用大型复杂数组,让一个数组变大 3 倍:

\n
In [49]: yy=np.array([xx,xx,xx]);yy.shape\nOut[49]: (3, 1300, 1300)\n\nIn [50]: timeit np.dot(xx,xx)\n794 ms \xc2\xb1 12.2 ms per loop (mean \xc2\xb1 std. dev. of 7 runs, 1 loop each)    \nIn [51]: timeit np.dot(xx,yy)       # (yy,xx) same timings\n55.5 s \xc2\xb1 151 ms per loop (mean \xc2\xb1 std. dev. of 7 runs, 1 loop each)\nIn [52]: timeit np.matmul(xx,yy)    # (yy,yy) same\n2.58 s \xc2\xb1 362 ms per loop (mean \xc2\xb1 std. dev. of 7 runs, 1 loop each)\n
Run Code Online (Sandbox Code Playgroud)\n

matmul刚刚把时间增加了3;dot到了 70 岁。我可以探索更多事物,但不能在分钟范围内进行计时。

\n