考虑以下代码
import timeit
import numpy as np
MyArray = np.empty((10000, 10000, 1))
print((MyArray.size, MyArray.shape, MyArray.dtype, np.isfortran(MyArray)))
print(timeit.timeit(lambda: MyArray[0], number=10000))
print(timeit.timeit(lambda: MyArray.take(0), number=10000))
MyTwoArrays = np.empty((10000, 10000, 2))
MyArray = np.split(MyTwoArrays, 2, axis=2)[0]
print((MyArray.size, MyArray.shape, MyArray.dtype, np.isfortran(MyArray)))
print(timeit.timeit(lambda: MyArray[0], number=10000))
print(timeit.timeit(lambda: MyArray.take(0), number=1))
及其在我系统上的输出:
(100000000, (10000, 10000, 1), dtype('float64'), False)
0.05690493136299911
0.06236779451013045
(100000000, (10000, 10000, 1), dtype('float64'), False)
0.0569617025453055
1.6303121549025763
MyArray
的两个版本具有相同的大小,形状,数据类型和数据排序。尽管如此,与使用numpy.take
结果的numpy.split
相比,获得第0个元素的速度要慢300,000倍,而使用相同结果的简单索引或使用“本机”numpy数组的numpy.take
。 / p>
为什么?我可以解决这个问题吗?
更新
这似乎与视图有关:MyArray = MyArray.copy()
修复了问题。尽管如此,我仍然感兴趣的是[0]
为什么同样快速地工作,而numpy.take
对视图的速度变慢。
另一次更新:
我注意到减速取决于数组维度(不是数组维度的数量,而是数组元素的数量)。对于单个第0个元素,我实现了长达8秒的访问时间。我发现这是这个问题最令人惊讶的方面。无论numpy.take在内部做什么,我都没有理由认为当索引更大时,为什么这个额外的“间接级别”应该更慢。
第三次更新:
根据@ hpaulj的评论,MyArray.take(0)
和MyArray[0]
不相同,这是一个更正后的代码示例。 (我通过我的MATLAB直觉犯了这个错误,并且一度停止验证我的最小例子。我不想替换原来的例子,因为hpaulj的答案可能取决于它。)
import timeit
import numpy as np
for UseSplit in (True, False):
if UseSplit:
print("Using split")
MyDoubleArray = np.random.rand(5000, 5000, 2)
MyArray = np.split(MyDoubleArray, 2, axis=2)[0]
else:
print("Not using split")
MyArray = np.random.rand(5000, 5000, 1)
print((MyArray.size, MyArray.shape, MyArray.dtype, np.isfortran(MyArray)))
NumpyTaking = MyArray.take(0)
DirectIndexing = MyArray.item(0)
assert (NumpyTaking == DirectIndexing)
print("Take 1")
print(timeit.timeit(lambda: MyArray.take(0), number=1))
print("Index 1")
print(timeit.timeit(lambda: MyArray.item(0), number=1))
NumpyTaking = MyArray.take(0, axis=2)
DirectIndexing = MyArray[:, :, 0]
assert (NumpyTaking == DirectIndexing).all()
print("Take many")
print(timeit.timeit(lambda: MyArray.take(0, axis=2), number=1))
print("Index many")
print(timeit.timeit(lambda: MyArray[:, :, 0], number=1))
在我的(其他)系统上使用此输出:
Using split
(25000000, (5000, 5000, 1), dtype('float64'), False)
Take 1
0.2260607502818708
Index 1
2.1667747519799052e-05
Take many
0.44334302173084994
Index many
0.0005971935325195243
Not using split
(25000000, (5000, 5000, 1), dtype('float64'), False)
Take 1
2.851019410510247e-05
Index 1
2.0527339755549434e-05
Take many
0.13906132276656846
Index many
1.444516501325488e-05
答案 0 :(得分:2)
这个答案有点冗长和复杂,但我认为关键点是,MyArray[0]
是两个结构中的一个视图。 MyArray.take
在第二种情况(split
)中复制。 copy
需要更长的时间。
这两项行动并不相同:
In [302]: MyArray = np.ones((10000, 10000, 1))
In [303]: MyArray[0].shape
Out[303]: (10000, 1)
In [304]: MyArray.take(0).shape
Out[304]: ()
带有take
(默认值)的 axis=None
会对数组进行ravels。指定轴将返回与MyArray[0,:,:]
相同的内容:
In [305]: MyArray.take(0,axis=0).shape
Out[305]: (10000, 1)
使用ipython
timeit
(和numpy 1.14)
In [306]: timeit MyArray[0].shape
425 ns ± 7.03 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
In [307]: timeit MyArray.take(0).shape
1.25 µs ± 11.2 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
In [308]: timeit MyArray.take(0,axis=0).shape
10.6 µs ± 22.2 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
take
速度慢得多,我感到有些惊讶,尽管我从来没有想过要成为速度工具的印象。相反,它对以下情况很方便:
In [311]: MyArray.take(0, axis=1).shape
Out[311]: (10000, 1)
In [313]: MyArray[:,0,:].shape
Out[313]: (10000, 1)
在代码中使用数字而不是冒号指定轴更容易。
但是,如果您需要沿给定轴的元素,则可以更容易使用。
当我通过拆分构建MyArray
时,take
时间会更糟糕
In [321]: timeit MyArray[0].shape
422 ns ± 5.54 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
In [322]: timeit MyArray.take(0).shape
713 ms ± 10.9 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [323]: timeit MyArray.take(0,axis=0).shape
705 ms ± 3.4 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
ravel
是大部分额外时间。我认为take
必须复制一份:
In [324]: timeit MyArray.ravel()
710 ms ± 19.1 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
构建步骤:np.ones((10000, 10000, 2))
需要更长时间,我担心会出现内存错误。我正在使用ones
而不是empty
来确保数组在使用前已完全分配。
这表明内存管理问题使时间变得复杂。
数据缓冲区指针告诉我数组是否是视图:
In [334]: MyArray.__array_interface__['data']
Out[334]: (139737581203472, False)
In [335]: MyArray2.__array_interface__['data']
Out[335]: (139737581203472, False)
MyArray2
就像您的MyTwoArrays
。因此,拆分返回视图,而不是副本。
但ravel
必须在分案中制作副本:
In [336]: MyArray.ravel().__array_interface__['data']
Out[336]: (139739981209616, False)
In [337]: MyArray2.ravel().__array_interface__['data']
Out[337]: (139737581203472, False)
查看数据缓冲区的索引与take
:
In [343]: MyArray[0].__array_interface__['data']
Out[343]: (139737581203472, False)
In [344]: MyArray.take(0, axis=0).__array_interface__['data']
Out[344]: (34066048, False)
In [345]: MyArray.take(0).__array_interface__['data']
Out[345]: (33320032, False)
MyArray[0]
仍然是一种观点,因此相对较快。
take
上的{p> MyArray
是axis
和In [346]: timeit MyArray.copy()
701 ms ± 1.87 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
的副本。
httpClient
我应该回去检查第一种情况下的复制,但是这次会话的内存负载正在拖累我的剩余计算。
答案 1 :(得分:1)
如果拆分矩阵而不复制数据(即视图),则需要为每个循环步骤创建一个需要解决的间接级别。这显然很慢,并解释了计算时间的大量增加。
唯一不会发生的情况是分割的后半部分是无效的,因为numpy视图总是删除单例列表的间接。如果使用以下行编辑代码,则可以观察到这一点:
take
现在第二个阵列的时间要快得多(虽然数据量仍然与以前相同)。
另一方面,如果您将一半数据复制到一个新阵列中,那么你就可以“平坦化”#34;这个间接,所以时间再次等同于非分裂数组。这是通常的时间与内存权衡
最后:为什么这个效果仅在take
时可见,而不是花哨的索引?我只能猜测(应该通过分析来源确认)花哨的索引更聪明地检测视图间接,并且能够在执行实际循环之前重新组织链接结构。这两种方法显然没有共享相同的代码,因为在我上面的split-with-void示例中,只有{{1}}函数被加速,而不是花哨的索引...