python和numpy:数组切片的求和

11 投票
4 回答
36872 浏览
提问于 2025-04-16 17:01

我有一个一维的numpy数组(array_)和一个Python列表(list_)。

下面的代码可以运行,但效率不高,因为切片操作会产生不必要的复制(对于Python列表来说肯定是这样,我觉得numpy数组也是如此):

result = sum(array_[1:])
result = sum(list_[1:])

有没有什么好的方法可以重写这个代码呢?

4 个回答

3

@Joe Kington(这是一个临时的回答,只是为了展示我的时间记录,我会很快删除它):

In []: x= arange(1e4)
In []: %timeit sum(x)
100000 loops, best of 3: 18.8 us per loop
In []: %timeit x.sum()
100000 loops, best of 3: 17.5 us per loop
In []: x= arange(1e5)
In []: %timeit sum(x)
10000 loops, best of 3: 165 us per loop
In []: %timeit x.sum()
10000 loops, best of 3: 158 us per loop
In []: x= arange(1e2)
In []: %timeit sum(x)
100000 loops, best of 3: 4.44 us per loop
In []: %timeit x.sum()
100000 loops, best of 3: 3.2 us per loop

根据我查到的numpy(1.5.1)的资料,sum(.)其实只是一个包装器,用来调用x.sum(.)。所以当输入数据变大时,sum(.)x.sum(.)的执行时间是差不多的(从大体上看)。

编辑:这个回答本来只是临时的,但实际上它(以及评论)可能对某些人有用。所以我就先不删了,直到有人真的要求我删除它。

8

我最初的想法和Joe Kington在处理列表时的想法一样,但我检查了一下,至少在我的机器上,islice的速度一直比较慢!

>>> timeit.timeit("sum(l[50:950])", "l = range(1000)", number=10000)
1.0398731231689453
>>> timeit.timeit("sum(islice(l, 50, 950))", "from itertools import islice; l = range(1000)", number=10000)
1.2317550182342529
>>> timeit.timeit("sum(l[50:950000])", "l = range(1000000)", number=10)
7.9020509719848633
>>> timeit.timeit("sum(islice(l, 50, 950000))", "from itertools import islice; l = range(1000000)", number=10)
8.4522969722747803

我尝试了一个custom_sum,发现它的速度更快,但也没快多少:

>>> setup = """
... def custom_sum(list, start, stop):
...     s = 0
...     for i in xrange(start, stop):
...         s += list[i]
...     return s
... 
... l = range(1000)
... """
>>> timeit.timeit("custom_sum(l, 50, 950)", setup, number=1000)
0.66767406463623047

而且,当数字变大时,它的速度慢得多!

>>> setup = setup.replace("range(1000)", "range(1000000)")
>>> timeit.timeit("custom_sum(l, 50, 950000)", setup, number=10)
14.185815095901489

我想不出其他可以测试的东西了。(有没有人有什么想法?)

15

切片一个numpy数组并不会像切片列表那样生成一个副本。

举个简单的例子:

import numpy as np
x = np.arange(100)
y = x[1:5]
y[:] = 1000
print x[:10]

这样做的结果是:

[   0 1000 1000 1000 1000    5    6    7    8    9]

即使我们修改了y中的值,它其实只是指向和x相同的内存。

切片一个ndarray会返回一个视图,而不会复制内存。

不过,使用array_[1:].sum()会比在numpy数组上调用Python内置的sum要高效得多。

简单比较一下:

In [28]: x = np.arange(10000)

In [29]: %timeit x.sum()
100000 loops, best of 3: 10.2 us per loop

In [30]: %timeit sum(x)
100 loops, best of 3: 4.01 ms per loop

补充说明:

如果在列表的情况下,出于某种原因你不想生成副本,可以使用itertools.islice。而不是:

result = sum(some_list[1:])

你可以这样做:

result = sum(itertools.islice(some_list, 1, None))

不过在大多数情况下,这种做法有点过于复杂。如果你处理的列表足够长,内存管理成为一个大问题,那么你可能不应该用列表来存储你的值。(列表并不是为了在内存中紧凑存储项目而设计的。)

另外,你也不想对numpy数组这样做。简单地使用some_array[1:].sum()会快得多,并且不会比islice占用更多的内存。

撰写回答