python & numpy: сумма среза массива

у меня есть 1-мерный массив numpy (array_) и список Python (list_).

следующий код работает, но неэффективен, потому что срезы включают ненужную копию (конечно, для списков Python, и я считаю также для массивов numpy?):

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

какой хороший способ переписать это?

4 ответов


нарезка массива 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() вместо вызова в Python строение sum на массиве numpy.

в качестве быстрого сравнения:

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

Edit:

в случае списка, если по какой-то причине вы не хотите делать копию, вы всегда можете использовать itertools.islice. Вместо:

result = sum(some_list[1:])

вы могли бы сделать:

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

в большинстве случаев это перебор, хотя. Если вы имеете дело со списками достаточно долго, что управление памятью является серьезной проблемой, то вы, вероятно, не следует использовать список хранить ваши ценности. (Списки не предназначены для компактного хранения элементов в памяти.)

кроме того, вы не хотели бы делать это для массива numpy. Просто делать some_array[1:].sum() будет на несколько порядков быстрее и не использовать любой больше памяти, чем islice.


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

Я не мог придумать ничего другого, чтобы проверить. (Мысли, кто-нибудь?)


@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(.).

редактировать: этот ответ должен был быть временным, но на самом деле он (и его комментарии) действительно может быть кому-то полезен. Так что я просто оставлю все как есть. теперь, пока кто-то действительно не попросит меня удалить его.


Я не нахожу x[1:].sum() значительно медленнее, чем x.sum(). Для списков sum(x) - x[0] быстрее sum(x[1:])(около 40% более быстрое ОММ).