列表缓慢(numpy.array)的原因是什么?

时间:2018-04-03 12:13:47

标签: python performance numpy

众所周知,如果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)版本比特殊功能3慢约tolist()

但是,将它与内置array - 模块的性能进行比较:

>>> 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 - 模块和tolist受益于small-integer-pool(即当值在[-5, 256]范围内时),但list(a)不是这种情况1}}:

##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

但仍然比慢速列表版本更快。

3 个答案:

答案 0 :(得分:2)

这是一个部分答案,解释了你对小整数池的观察:

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

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

确实,tolist(来源here)的C实施使用PyArray_GETITEM which is the equivalent to Python arr[index].item(), not - as one might assume - arr[index]

答案 1 :(得分:1)

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

我没有进行系统的分析,但是当在调试器中停止时,每次两个版本都在创建整数对象的例程中,所以很可能这是执行时间的最大部分。花费了,而且开销并没有发挥重要作用。

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

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

与Python3相比,Python2有一个小的区别:Python2有两个不同的整数类:一个是更高效的,对于高达32位的数字和一个用于任意大数的一个 - 因此对于更大的数字,最通用的(必须采取更昂贵的路径 - 这也可以观察到。

答案 2 :(得分:0)

使用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数组很灵活。它们可以具有任意数量的维度,具有各种步幅。数组数组总是平坦的。这种灵活性可能需要付出代价,这体现在更复杂的迭代中。