【问题标题】:Why use numpy over list based on speed?为什么要根据速度使用 numpy over list?
【发布时间】:2017-10-21 07:16:33
【问题描述】:

参考Why NumPy instead of Python lists?

tom10 说:

速度:这是一个对列表和 NumPy 数组求和的测试,显示 NumPy 数组上的求和快 10 倍(在此测试中,里程可能会有所不同)。

但我的测试使用以下代码:

import numpy as np
import time as time

N = 100000

#using numpy
start = time.time()
array = np.array([])

for i in range(N):
    array = np.append(array, i)

end = time.time()
print ("Using numpy: ", round(end-start, 2), end="\n")

#using list
start = time.time()
list = []

for i in range(N):
    list.append(i)

list = np.array(list)   
end = time.time()
print ("Using list : ", round(end-start, 2), end="\n")

给出结果:

Using numpy: 8.35
Using list : 0.02

使用“append”时,list确实比numpy好?

【问题讨论】:

  • 是的,.append 对于 list 对象是恒定(摊销)恒定时间,对于 numpy.ndarray 对象是线性时间
  • 我有什么办法可以像list 一样做.appendnumpy.ndarray
  • Numpy 数组被设计用于保存潜在的多维矩阵,其中附加通常不能像简单的一维情况那样有效。通常在代码中,您会看到 np.zeros(shape) 调用提前分配了足够的元素,您已经知道数据的大小。如果您需要经常追加,您可能应该坚持使用内置列表。
  • 人们经常将数据收集到 Vanilla Python 列表中,只有在需要处理时才会生成 numpy.array
  • @coldspeed, numpy 列表是对象引用的数组追加是常量,因为列表对象保留的空间比列表中的项目多,因此它可以添加项目而不需要额外的内存。见docs.python.org/2/faq/design.html#how-are-lists-implemented

标签: python arrays numpy


【解决方案1】:

回答你的问题,是的。追加到数组是一项昂贵的操作,而列表则相对便宜(请参阅Internals of Python list, access and resizing runtimes 了解原因)。但是,这不是放弃 numpy 的理由。还有其他方法可以轻松地将数据添加到 numpy 数组中。

有很多方法可以做到这一点(无论如何,对我来说)。跳到底部查看每个基准的基准。

可能最常见的方法是简单地预先分配数组,并对其进行索引,

#using preallocated numpy
start = time.time()
array = np.zeros(N)

for i in range(N):
    array[i] = i

end = time.time()
print ("Using preallocated numpy: ", round(end-start, 5), end="\n")

当然,您也可以为列表预分配内存,因此让我们将其包含在基准比较中。

#using preallocated list
start = time.time()
res = [None]*N

for i in range(N):
    res[i] = i

res = np.array(res)
end = time.time()
print ("Using preallocated list : ", round(end-start, 5), end="\n")

根据您的代码,使用 numpy 的 fromiter 函数也可能会有所帮助,该函数使用迭代器的结果来初始化数组。

#using numpy fromiter shortcut
start = time.time()

res = np.fromiter(range(N), dtype='float64') # Use same dtype as other tests

end = time.time()
print ("Using fromiter : ", round(end-start, 5), end="\n")

当然,使用内置迭代器并不是很灵活,所以我们也试试自定义迭代器,

#using custom iterator
start = time.time()
def it(N):
    i = 0
    while i < N:
        yield i
        i += 1

res = np.fromiter(it(N), dtype='float64') # Use same dtype as other tests

end = time.time()
print ("Using custom iterator : ", round(end-start, 5), end="\n")

这是使用numpy 的两种非常灵活的方式。第一种,使用预分配的数组,是最灵活的。让我们看看它们的比较:

Using numpy:  2.40017
Using list :  0.0164
Using preallocated numpy:  0.01604
Using preallocated list :  0.01322
Using fromiter :  0.00577
Using custom iterator :  0.01458

马上,您可以看到预分配使numpy 比使用列表快得多,尽管预分配列表使两者的速度大致相同。使用内置迭代器非常快,尽管迭代器性能 当使用自定义迭代器时,回落到预分配数组和列表的范围内。

将代码直接转换为numpy 通常性能很差,就像append 一样。使用numpy 的方法找到一种方法几乎总能带来实质性的改进。在这种情况下,预先分配数组或将每个元素的计算表示为迭代器以获得与 python 列表相似的性能。或者使用香草 python 列表,因为性能大致相同。

编辑:原始答案还包括np.fromfunction。这被删除了,因为它不适合一次添加一个元素的用例,fromfunction 实际上初始化了数组并使用numpy 的广播来进行单个函数调用。它快了大约一百倍,所以如果你能用广播解决你的问题,就不要费心这些其他方法了。

【讨论】:

  • fromfunction 实际上并没有对每个条目进行单独的函数调用;它假设函数广播,并以数组作为输入调用函数一次,期望函数产生一个将直接返回的数组。 (文档在这方面不是很清楚,但至少在 1.13 中它们更清晰;它们曾经更糟糕。)
  • @user2357112 谢谢,我没有意识到它是如何工作的。这比我想象的要少得多。我已经更新了帖子。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-08-03
  • 2016-08-13
  • 1970-01-01
相关资源
最近更新 更多