numpy矩阵乘法的奇怪性能结果

时间:2014-07-09 19:54:49

标签: python numpy matrix matrix-multiplication

最近我发现了一个矩阵乘法与numpy表现出非常奇怪的表现的情况(至少对我而言)。为了说明这一点,我创建了一个这样的矩阵的例子和一个简单的脚本来演示时间。两者都可以从the repo下载,我不在这里包含脚本,因为没有数据它几乎没用。

脚本使用shape函数和{{,以不同方式将两对矩阵(每个对在dtypedot中相同,只有数据不同)相乘1}}。实际上,我注意到了几个异常现象:

  • 第一对(einsum)乘以比第二对(A * B)快得多。
  • 当我将所有矩阵转换为C * D时,两个对的时间变得相同:比乘以float64的时间长,但比A * B短。
  • 这些效果仍然适用于C * D(我理解为numpy实现)和einsum(在我的机器上使用BLAS)。 为了完整起见,我在笔记本电脑上输出了这个脚本:
dot

如何解释此类结果,以及如何将With np.dot: A * B: 0.142910003662 s C * D: 4.9057161808 s A * D: 0.20524597168 s C * B: 4.20220398903 s A * B (to float32): 0.156805992126 s C * D (to float32): 5.11792707443 s A * B (to float64): 0.52608704567 s C * D (to float64): 0.484733819962 s A * B (to float64 to float32): 0.255760908127 s C * D (to float64 to float32): 4.7677090168 s With einsum: A * B: 0.489732980728 s C * D: 7.34477996826 s A * D: 0.449800014496 s C * B: 4.05954909325 s A * B (to float32): 0.411967992783 s C * D (to float32): 7.32073783875 s A * B (to float64): 0.80580997467 s C * D (to float64): 0.808521032333 s A * B (to float64 to float32): 0.414498090744 s C * D (to float64 to float32): 7.32472801208 s 加倍C * D

2 个答案:

答案 0 :(得分:4)

您所看到的放缓是由于涉及subnormal numbers的计算所致。使用低于正常输入或输出的算术运算时,许多处理器速度要慢得多。有几个相关的StackOverflow问题:请参阅this related C# question(特别是Eric Postpischil的answer)和this answer到C ++问题以获取更多信息。

在您的特定情况下,矩阵C(dtype为float32)包含多个次正规数。对于单精度浮点数,子正常/正常边界为2^-126,或约为1.18e-38。这是我在C看到的内容:

>>> ((0 < abs(C)) & (abs(C) < 2.0**-126)).sum()  # number of subnormal entries
44694
>>> C.size
682450

因此,大约6.5%的C条目是次正规的,这足以减慢C*BC*D次乘法的速度。相反,AB不会接近次正规边界:

>>> abs(A[A != 0]).min()
4.6801152e-12
>>> abs(B[B != 0]).min()
4.0640174e-07

因此,A*B矩阵乘法中涉及的任何中间值都不是正常的,并且不适用速度惩罚。

关于你问题的第二部分,我不确定该建议什么。如果你足够努力,并且你正在使用x64 / SSE2(而不是x87 FPU),你可以设置Python中的flush-to-zero和denormals-are-zero标志。有关粗略和非便携式基于ctypes的黑客攻击,请参阅this answer;如果你真的想要遵循这条路线,写一个自定义的C扩展来做这个可能是一个更好的选择。

我很想尝试缩放C以使其完全进入正常范围(并将单个产品从C*D带入正常范围),但这可能不会如果C在浮点范围的上限处也有值,则可以。或者,简单地用{0}替换C中的微小值可能会有效,但是由此产生的精度损失是否显着和/或可接受将取决于您的应用。

答案 1 :(得分:1)

Mark Dickinson已经回答了你的问题,但只是为了好玩,试试这个:

Cp = np.array(list(C[:,0]))
Ap = np.array(list(A[:,0]))

这将消除拼接延迟,并确保数组在内存中相似。

%timeit Cp * Cp   % 34.9 us per loop
%timeit Ap * Ap   % 3.59 us per loop

糟糕。