51

我正在寻找最有效的方法来确定大型数组包含至少一个非零值。乍一看np.任何看起来像是这显然是一种工具,但在大型阵列上运行速度似乎出乎意料地慢。

考虑这种极端情况:

first=np.zeros(1E3,dtype=np.bool)last=np.zeros(1E3,dtype=np.bool)first[0]=真last[-1]=真#测试1%timeit np.any(第一个)>>>100000个回路,三选一:每个回路6.36 us#测试2%timeit np.any(最后)>>>100000个回路,每回路最佳3:6.95 us

至少不适用似乎在做一些模糊合理的事情-如果非零值是数组中的第一个值,不需要考虑回来之前还有其他的吗真的,所以我希望测试1稍微比测试2快。

然而,当我们将数组变得更大时会发生什么?

第一个=np.零(1E9,数据类型=np.bool)last=np.zeros(1E9,dtype=np.bool)first[0]=真last[-1]=真#测试3%timeit np.any(第一个)>>>10个回路,最好为3:21.6 ms/回路#测试4%timeit np.any(最后)>>>1个回路,每个回路最好为3:739 ms

正如预期的那样,测试4比测试3慢得多。然而,在测试3中np.任何仍然只需检查中单个元素的值第一为了知道它至少包含一个非零值。为什么?那么,测试3比测试1慢得多吗?

编辑1:

我使用的是Numpy的开发版本(1.8.0.dev-e11cd9b),但我使用Numpy 1.7.1获得了完全相同的计时结果。我运行的是64位Linux,Python 2.7.4。我的系统基本上处于空闲状态(我正在运行IPython会话、浏览器和文本编辑器),我肯定没有进行交换。我还在另一台运行Numpy 1.7.1的机器上复制了结果。

编辑2:

使用Numpy 1.6.2,测试1和测试3的时间都是~1.85us,因此正如jorgeca所说,Numpy 1.6.2和1.7.11.7.0。

编辑3:

在J.F.Sebastian和jorgeca的带领下,我使用np.全部在一个零数组上,它应该等价于调用np.任何在第一个元素为一的数组上。

测试脚本:

导入timeit将numpy导入为np打印“Numpy v%s”%np.version.full_versionstmt=“np.all(x)”对于xrange(10)中的ii:setup=“将numpy导入为np;x=np.zeros(%d,dtype=np.bool)”%(10**ii)timer=计时。计时器(语句、设置)n、 r=1,3t=np.min(计时器重复(r,n))当t<0.2时:n*=10t=np.min(计时器重复(r,n))t/=n如果t<1E-3:timestr=“%1.3f我们”%(t*1E6)elif t<1:时间tr=“%1.3f毫秒”%(t*1E3)其他:timestr=“%1.3f s”%t打印“数组大小:1E%i,%i循环,%i:%s/loop中的最佳”%(ii,n,r,timestr)

结果:

Numpy 1.6.2版阵列大小:1E0,1000000个环路,三选一:1.738 us/环路阵列大小:1E1000000个环路,三选一:1.845 us/环路阵列大小:1E2,1000000个环路,三选一:1.862 us/环路阵列大小:1E3,1000000个环路,三选一:1.858 us/环路阵列大小:1E4,1000000个环路,三选一:1.864 us/环路阵列大小:1E5,1000000个环路,三选一:1.882 us/环路阵列大小:1E6,1000000个环路,三选一:1.866 us/环路阵列大小:1E7,1000000个环路,三选一:1.853 us/环路阵列大小:1E8,1000000个环路,三选一:1.860 us/环路阵列大小:1E9,1000000个环路,三选一:1.854 us/环路Numpy 1.7.0版阵列大小:1E0,100000个环路,三选一:5.881 us/环路阵列大小:1E1,100000个环路,三选一:5.831 us/环路阵列大小:1E2,100000个环路,三选一:5.924 us/环路阵列大小:1E3,100000个环路,三选一:5.864 us/环路阵列大小:1E4,100000个环路,三选一:5.997 us/环路阵列大小:1E5,100000个环路,三局三胜制:6.979 us/环路阵列大小:1E6,100000个环路,三选一:17.196 us/环路阵列大小:1E7,10000个环路,最好的3:116.162 us/环路阵列大小:1E8,1000个环路,最佳3:1.112 ms/环路阵列大小:1E9,100个环路,三选一:11.061 ms/环路Numpy v1.7.1版阵列大小:1E0,100000个环路,三选一:6.216 us/环路阵列大小:1E1,100000个环路,三取三:6.257 us/环路阵列大小:1E2,100000个环路,三选一:6.318 us/环路阵列大小:1E3,100000个环路,三选一:6.247 us/环路阵列大小:1E4,100000个环路,三选一:6.492 us/环路阵列大小:1E5,100000个环路,三选一:7.406 us/环路阵列大小:1E6,100000个环路,三选一:17.426 us/环路阵列大小:1E7,10000个环路,三选一:115.946 us/环路阵列大小:1E8,1000个环路,最佳3:1.102 ms/环路阵列大小:1E9,100个环路,三选一:10.987 ms/环路Numpy v1.8.0.dev-e11cd9b版阵列大小:1E0,100000个环路,三选一:6.357 us/环路阵列大小:1E1,100000个环路,三取三:6.399 us/环路阵列大小:1E2,100000个环路,三选一:6.425 us/环路阵列大小:1E3,100000个环路,三选一:6.397 us/环路阵列大小:1E4,100000个环路,三选一:6.596 us/环路阵列大小:1E5,100000个环路,三选一:7.569 us/环路阵列大小:1E6,100000个环路,三选一:17.445 us/环路阵列大小:1E7,10000个环路,三选一:115.109 us/环路阵列大小:1E8,1000个环路,三选一:1.094 ms/环路阵列大小:1E9,100个环路,三选一:10.840 ms/环路

编辑4:

在seberg的评论之后,我用一个净浮动32数组而不是np.池在这种情况下,Numpy 1.6.2也会随着阵列大小的增加而减速:

Numpy 1.6.2版阵列大小:1E0,100000个环路,三选一:3.503 us/环路阵列大小:1E1,100000个环路,三选一:3.597 us/环路阵列大小:1E2,100000个环路,三选一:3.742 us/环路阵列大小:1E3,100000个环路,三选一:4.745 us/环路阵列大小:1E4,100000个环路,三取三:14.533 us/环路阵列大小:1E5,10000个环路,最好的3:112.463 us/环路阵列大小:1E6,1000个环路,最佳3:1.101 ms/环路阵列大小:1E7,100个环路,三选一:11.724 ms/环路阵列大小:1E8,10个环路,最好为3:116.924 ms/环路阵列大小:1E9,1个环路,最好为3:1.168 s/环路Numpy v1.7.1版阵列大小:1E0,100000个环路,三取三:6.548 us/环路阵列大小:1E1,100000个环路,三选一:6.546 us/环路阵列大小:1E2,100000个环路,三选一:6.804 us/环路阵列大小:1E3,100000个环路,三选一:7.784 us/环路阵列大小:1E4,100000个环路,三选一:17.946 us/环路阵列大小:1E5,10000个环路,最好的3:117.235 us/环路阵列大小:1E6,1000个环路,三选一:1.096 ms/环路阵列大小:1E7,100个环路,三选一:12.328 ms/环路阵列大小:1E8,10个环路,3中最佳:118.431 ms/环路阵列大小:1E9,1个环路,最好为3:1.172 s/环路

为什么会发生这种情况?与布尔情况一样,np.全部在返回之前,仍然只需要检查第一个元素,因此时间应该仍然是恒定的w.r.t.数组大小。

13
  • 2
    我无法在numpy 1.6.2中复制它:在这两种情况下,我第一次获得2µs。在这两种情况下,您的计算机是否都处于空闲状态/没有交换? 评论 2013年6月15日22:15
  • 2
    好吧,我也可以在开发版本中复制它,所以我想在1.6.2和1.7.1之间存在性能回归。 评论 2013年6月15日22:50
  • 2
    时间恒定到1e4-1e5项,然后开始增加(几乎是线性的1e6-1e9)。中可能有一些优化np.all()对于需要与大小成比例的工作的大量项目,这会触发。
    – jfs公司
    评论 2013年6月15日23:23
  • 1
    我不会太在意它,因为这样的事情只会影响纯布尔数组(老实说,您通常会先计算这些数组)。很可能新机器总是把ufunc挤在一起,因为它从来没有真正疼过。请注意,开销是微小的,对于这些时间,它仍然比完整扫描快60倍。
    – 塞伯格
    评论 2013年6月16日8:27
  • 1
    这就是说,我猜它会在不需要缓冲的情况下将最里面的维度扩展到最大可能的大小,所以机器中可能有一个错误导致它无法做到这一点。如果你介意的话,请阅读nditer代码,我认为它在某些地方试图扩展。
    – 塞伯格
    评论 2013年6月16日8:39

1答案1

重置为默认值
33

正如评论中所猜测的那样,我可以确认数组的处理是分块完成的。首先,我将向您展示代码中的内容,然后向您展示如何更改块大小以及这样做对基准测试的影响。

在Numpy源文件中的何处查找缩减处理

np.all(x)与x.all()相同。all()实际上调用np.core.umath.logical_and.reduce(x)。

如果你想深入研究numpy源代码,我会尝试引导你找到使用了缓冲区/块大小的方法。包含我们将要查看的所有代码的文件夹是numpy/core/src/umath/。

ufunc_object.c中的PyUFunc_Reduce()是处理Reduce的c函数。在PyUFunc_Reduce()中,块或缓冲区大小是通过PyUFunc_GetPyValues()函数(ufunc_object.c)在某些全局字典中查找Reduce的值来找到的。在我的机器上,从开发分支编译,块大小为8192。调用reduction.c中的PyUFunc_ReduceWrapper()来设置迭代器(步幅等于块大小),并调用传入的循环函数,该函数是ufunc_object.c中reduce_loop()。

reduce_loop()基本上只使用迭代器并为每个块调用另一个innerloop()函数。内部循环函数位于loops.src中。对于布尔数组和all/logical_and的情况,合适的内环函数是BOOL_logical_aand。您可以通过搜索BOOLEAN LOOPS找到正确的函数,然后它就是下面的第二个函数(由于这里使用的是类似模板的编程,所以很难找到)。在那里,你会发现短路实际上是针对每个块进行的。

如何更改ufunctions(以及any/all)中使用的缓冲区大小

可以使用np.getbuffersize()获取块/缓冲区大小。对我来说,它返回8192,而没有手动设置它,它与我在代码中打印出的缓冲区大小相匹配。可以使用np.setbuffersize()更改块大小。

使用更大缓冲区大小的结果

我将您的基准代码更改为以下内容:

导入timeit将numpy导入为np打印“Numpy v%s”%np.version.full_versionstmt=“np.all(x)”对于xrange(9)中的ii:setup=“将numpy导入为np;x=np.zeros(%d,dtype=np.bool);np.setbufsize(%d)”%(10**ii,最大值(8192,最小值(10**i,10**7))计时器=timeit。计时器(语句、设置)n、 r=1,3t=np.min(计时器重复(r,n))当t<0.2时:n*=10t=np.min(计时器重复(r,n))t/=n如果t<1E-3:timestr=“%1.3f我们”%(t*1E6)elif t<1:timestr=“%1.3f ms”%(t*1E3)其他:timestr=“%1.3f s”%t打印“数组大小:1E%i,%i循环,%i:%s/loop中的最佳”%(ii,n,r,timestr)

Numpy不喜欢缓冲区大小太小或太大,所以我确保它不会小于8192或大于1E7,因为Numpy并不喜欢1E8的缓冲区大小。否则,我将缓冲区大小设置为正在处理的数组的大小。我只升到了1E8,因为我的机器目前只有4GB的内存。结果如下:

Numpy v1.8.0.dev-2a5c2c8版阵列大小:1E0,100000个环路,三选一:5.351 us/环路阵列大小:1E1,100000个环路,三选一:5.390 us/环路阵列大小:1E2,100000个环路,三选一:5.366 us/环路阵列大小:1E3,100000个环路,三选一:5.360 us/环路阵列大小:1E4,100000个环路,三取三:5.433 us/环路阵列大小:1E5,100000个环路,三选一:5.400 us/环路阵列大小:1E6,100000个环路,三选一:5.397 us/环路阵列大小:1E7,100000个环路,三选一:5.381 us/环路阵列大小:1E8,100000个环路,三选一:6.126 us/环路

由于缓冲区大小的限制,有多个块正在被处理,因此在最后一次计时中出现了小幅度的上升。

0

你的答案

单击“发布您的答案”,表示您同意我们的服务条款并确认您已阅读我们的隐私政策.

不是你想要的答案吗?浏览标记的其他问题问你自己的问题.