繁体   English   中英

list(numpy.array)变慢的原因是什么?

[英]What is the reason for slowness of list(numpy.array)?

众所周知,如果a是一个numpy数组,则a.tolist()list(a)更快,例如:

>>> import numpy as np
>>> big_np=np.random.randint(1,10**7,(10**7,))

>>> %timeit list(big_np)
1 loop, best of 3: 869 ms per loop
>>> %timeit big_np.tolist()
1 loop, best of 3: 306 ms per loop

这意味着,朴素的list(a)版本比特殊功能tolist()慢约3 tolist()

但是,将其与内置array -module的性能进行比较:

>>> import array
>>> big_arr=array.array('i', big_np)
>>> %timeit list(big_arr)
1 loop, best of 3: 312 ms per loop

我们可以看到,也许应该说list(a)慢而不是tolist()快,因为array.array和特殊函数一样快。

另一个观察结果: array.array -module和tolist受益于small-integer-pool (即,当值在[-5, 256] tolist [-5, 256]范围内时),但是list(a)并非如此:

##only small integers:
>>> small_np=np.random.randint(1,250, (10**7,))
>>> small_arr=array.array('i', small_np)

>>> %timeit list(small_np)
1 loop, best of 3: 873 ms per loop
>>> %timeit small_np.tolist()
10 loops, best of 3: 188 ms per loop
>>> %timeit list(small_arr)
10 loops, best of 3: 150 ms per loop

我们可以看到,较快的版本比以前快2倍左右,但较慢的版本和以前一样慢。

我的问题:与list(numpy.array)相比, list(array.array)速度降低了多少?

编辑:

再观察一下,对于Python2.7,如果整数更大(即不能由int32保持),则需要更长的时间:

>>> very_big=np.random.randint(1,10**7,(10**7,))+10**17
>>> not_so_big=np.random.randint(1,10**7,(10**7,))+10**9
>>> %timeit very_big.tolist()
1 loop, best of 3: 627 ms per loop
>>> %timeit not_so_big.tolist()
1 loop, best of 3: 302 ms per loop

但仍比缓慢的列表版本更快。

这是部分答案,解释您对小型整数池的观察:

>>> a = np.arange(10)
>>> type(list(a)[0])
<class 'numpy.int64'>
>>> type(a.tolist()[0])
<class 'int'>

正如我们所看到的, tolist似乎尝试创建本机python类型的元素,而数组迭代器(列表构造函数使用的数组)不会打扰。

确实, tolist的C实现( 在此处提供源代码)使用PyArray_GETITEM ,它等效于Python arr[index].item() ,而不是-可能有人假设arr[index].item() arr[index]

基本上,Paul Panzer的答案解释了会发生的情况:在慢list(...)版本中,列表的结果元素不是python-integers,而是numpy- numpy.int64 ,例如numpy.int64 这个答案只说明一点点,并连接点。

我没有进行系统的分析,但是当在调试器中停止运行时,每次两个版本都在创建整数对象的例程中,因此很可能这是执行时间的主要部分,并且开销并不重要。

list(..) version迭代器调用array_item ,它对一维数组有特殊处理,并调用PyArray_Scalar ,这是一个非常通用的函数,并且不使用Pythons-integer-creation的机制。 它恰好比Python版本慢,对于小值也没有整数池。

.tolist()版本调用recursive_tolist ,该版本最终使用Python的PyLong_FromLong(long) ,它显示了所有观察到的行为,并且碰巧比numpy功能更快(可能是因为这不是使用numpy的正常方式,没有很多优化措施)完成)。

与Python3相比,Python2的差别很小:Python2具有两种不同的整数类别:一类效率更高,适用于32位以下的数字,另一类适用于任意大数-因此,对于较大的数字,最通用的(因此更多昂贵)的路径必须走-这也是可以观察到的。

使用list(something)构造一个列表会迭代某些内容,并将迭代结果收集到一个新列表中。

如果list(small_np)list(small_arr)慢,则可以假定对small_np迭代要比对small_arr迭代慢。 让我们验证一下:

%timeit for i in small_np: pass   # 1 loop, best of 3: 916 ms per loop
%timeit for i in small_arr: pass  # 1 loop, best of 3: 261 ms per loop

是的,迭代一个numpy数组似乎比较慢。 这是我必须开始推测的地方。 numpy数组很灵活。 它们可以具有任意数量的尺寸,并具有不同的跨度。 数组数组总是平坦的。 这种灵活性可能需要付出一定的代价,这体现在更复杂的迭代中。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM