Python中的numpy数组与列表有什么区别?

13

我需要对一个大量数字的列表进行一些计算。

array.array或numpy.array是否比普通数组提供了显著的性能提升?

我不必对数组进行复杂的操作,只需要能够访问和修改值,例如:

import numpy
x = numpy.array([0] * 1000000)
for i in range(1,len(x)):
  x[i] = x[i-1] + i

所以我不需要真正使用拼接、切片等操作。

此外,如果我尝试分配不适合C long的值,似乎数组会抛出错误:

import numpy
a = numpy.array([0])
a[0] += 1232234234234324353453453
print(a)

在控制台上我看到:

a[0] += 1232234234234324353453453
OverflowError: Python int too large to convert to C long

是否有一种数组的变体可以让我放入无界限制的Python整数?或者用这种方式做会否削弱数组的存在意义?


8个回答

15

首先需要了解数组和列表之间的区别。

数组是一段连续的内存块,由某种类型 (例如整数) 的元素组成。

创建后您不能更改数组的大小。因此,数组中每个整数元素都有一个固定大小,例如 4 个字节。

另一方面,列表只是一个地址的 "数组"(它们也有固定的大小)。

但是,每个元素存储的是内存中某个其他位置的整数的地址,这就是您想要处理的实际整数。当然,该整数的大小与数组的大小无关。因此,您始终可以创建一个新的(更大的)整数,并“替换”旧整数,而不会影响数组的大小,数组仅仅保存了一个整数的地址

当然,列表的这种便利性是有代价的:对整数执行算术运算现在需要访问数组的内存,加上访问整数本身的内存,再加上分配更多内存所需的时间(如果需要),加上删除旧整数所需的时间(如果需要)。因此,它可能会变慢,因此您必须小心处理数组中的每个整数。


如果您不使用向量化操作(如 OP 的第一个示例中所示),最后一段同样适用于固定大小整数的 numpy 数组。这并不是 numpy 数组可以更快的原因。 - jfs

9

您的第一个示例可以加速。Python循环和numpy数组中单个项的访问速度较慢。请改用矢量化操作:

import numpy as np
x = np.arange(1000000).cumsum()

您可以将无限制的Python整数放入numpy数组中:

a = np.array([0], dtype=object)
a[0] += 1232234234234324353453453

在这种情况下,与固定大小的C整数相比,算术运算速度会变慢。

5

大多数情况下,列表是有用的。有时候使用numpy数组可能更方便,例如:

a=[1,2,3,4,5,6,7,8,9,10]

b=[5,8,9]

考虑一个列表'a',如果你想访问列表'b'中给定的离散索引处的元素,请编写:
a[b]

不会起作用。

但是当您将它们用作数组时,您只需编写

a[b]

为了得到这种形式的输出,需要使用以下代码:array([6,9,10])。

2

array.array或numpy.array相对于典型数组是否提供了显著的性能提升?

我尝试使用以下代码进行测试:

import timeit, math, array
from functools import partial
import numpy as np

# from the question
def calc1(x):
    for i in range(1,len(x)):
        x[i] = x[i-1] + 1

# a floating point operation
def calc2(x):
    for i in range(0,len(x)):
        x[i] = math.sin(i)

L = int(1e5)

# np
print('np 1: {:.5f} s'.format(timeit.timeit(partial(calc1, np.array([0] * L)), number=20)))
print('np 2: {:.5f} s'.format(timeit.timeit(partial(calc2, np.array([0] * L)), number=20)))

# np but with vectorized form
vfunc = np.vectorize(math.sin)
print('np 2 vectorized: {:.5f} s'.format(timeit.timeit(partial(vfunc, np.arange(0, L)), number=20)))

# with list
print('list 1: {:.5f} s'.format(timeit.timeit(partial(calc1, [0] * L), number=20)))
print('list 2: {:.5f} s'.format(timeit.timeit(partial(calc2, [0] * L), number=20)))

# with array
print('array 1: {:.5f} s'.format(timeit.timeit(partial(calc1, array.array("f", [0] * L)), number=20)))
print('array 2: {:.5f} s'.format(timeit.timeit(partial(calc2, array.array("f", [0] * L)), number=20)))

结果表明在这里list执行速度最快(Python 3.3,NumPy 1.8):

np 1: 2.14277 s
np 2: 0.77008 s
np 2 vectorized: 0.44117 s
list 1: 0.29795 s
list 2: 0.66529 s
array 1: 0.66134 s
array 2: 0.88299 s

这似乎是违反直觉的。对于这些简单的示例,使用numpyarray似乎没有任何优势。


2

针对您的使用情况,请使用列表。

我的规则是,考虑到健壮性和速度,何时使用哪个:

列表:(最健壮,对于可变的情况速度最快) 例如,在物理模拟中,当您的列表不断变化时。当您从头开始“创建”可能是不可预测的数据时。

np.arrary:(健壮性较低,用于线性代数和数据后处理速度最快) 例如,在您已经通过传感器或模拟收集了数据集之后,“后处理”数据集;执行可以矢量化的操作。


0
数组.array或numpy.array是否比典型的数组提供显著的性能提升?
这取决于你正在做什么。
用这种方式做会削弱拥有数组的初衷。

0

使用 a=numpy.array(number_of_elements, dtype=numpy.int64)可以创建一个包含64位整数的数组。这些数组能够存储在-2^63到(2^63)-1之间的任何整数(大约在-10^19和10^19之间),通常已经足够。


0
应用向量化的numpy似乎受到timeit或partial的影响。请尝试以下方法:
`# not using time it
total_time=0
for i in range(20):
    t0=time.process_time()
    np.sin(np.arange(0,L))
    total_time+=time.process_time()-t0
    print('np 2 vectorized - no timeit or partial: {:.5f} s'.format(total_time/20))`

结果:

np 1: 0.67521 s
np 2: 0.39538 s
np 2 vectorized: 0.26964 s
list 1: 0.19755 s
list 2: 0.26914 s
array 1: 0.27625 s
array 2: 0.35779 s
np 2 vectorized - no timeit or partial: 0.00077 s

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接