Подтвердить что ты не робот

Сумма Python против NumPy numpy.sum

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

>>> import numpy as np
>>> np_a = np.array(range(5))
>>> np_a
array([0, 1, 2, 3, 4])
>>> type(np_a)
<class 'numpy.ndarray')

>>> py_a = list(range(5))
>>> py_a
[0, 1, 2, 3, 4]
>>> type(py_a)
<class 'list'>

# The numerical answer (10) is the same for the following sums:
>>> type(np.sum(np_a))
<class 'numpy.int32'>
>>> type(sum(np_a))
<class 'numpy.int32'>
>>> type(np.sum(py_a))
<class 'numpy.int32'>
>>> type(sum(py_a))
<class 'int'>

Изменить: Я думаю, что мой практический вопрос здесь будет заключаться в использовании numpy.sum в списке целых чисел Python, который будет быстрее, чем использование собственного sum

Python <

Кроме того, каковы последствия (включая производительность) использования целого числа Python по сравнению со скаляром numpy.int32? Например, для a += 1 существует ли поведение или разность производительности, если тип a является целым числом Python или numpy.int32? Мне любопытно, если быстрее использовать скалярный тип NumPy типа numpy.int32 для значения, которое добавлено или вычитано много в коде Python.

Для пояснения я работаю над симуляцией биоинформатики, которая частично состоит из коллапсирующего многомерного numpy.ndarray в одиночные скалярные суммы, которые затем дополнительно обрабатываются. Я использую Python 3.2 и NumPy 1.6.

Спасибо заранее!

4b9b3361

Ответ 1

Мне стало любопытно и приурочено. numpy.sum кажется намного быстрее для массивов numpy, но намного медленнее в списках.

import numpy as np
import timeit

x = range(1000)
# or 
#x = np.random.standard_normal(1000)

def pure_sum():
    return sum(x)

def numpy_sum():
    return np.sum(x)

n = 10000

t1 = timeit.timeit(pure_sum, number = n)
print 'Pure Python Sum:', t1
t2 = timeit.timeit(numpy_sum, number = n)
print 'Numpy Sum:', t2

Результат, когда x = range(1000):

Pure Python Sum: 0.445913167735
Numpy Sum: 8.54926219673

Результат, когда x = np.random.standard_normal(1000):

Pure Python Sum: 12.1442425643
Numpy Sum: 0.303303771848

Я использую Python 2.7.2 и Numpy 1.6.1

Ответ 2

[...] мой [...] вопрос заключается в том, будет ли использование numpy.sum в списке целых чисел Python быстрее, чем использование собственной sum Python?

Ответ на этот вопрос: Нет.

Сумма питонов будет быстрее в списках, а сумма NumPys будет быстрее в массивах. Я фактически сделал тест, чтобы показать время (Python 3.6, NumPy 1.14):

import random
import numpy as np
import matplotlib.pyplot as plt

from simple_benchmark import benchmark

%matplotlib notebook

def numpy_sum(it):
    return np.sum(it)

def python_sum(it):
    return sum(it)

def numpy_sum_method(arr):
    return arr.sum()

b_array = benchmark(
    [numpy_sum, numpy_sum_method, python_sum],
    arguments={2**i: np.random.randint(0, 10, 2**i) for i in range(2, 21)},
    argument_name='array size',
    function_aliases={numpy_sum: 'numpy.sum(<array>)', numpy_sum_method: '<array>.sum()', python_sum: "sum(<array>)"}
)

b_list = benchmark(
    [numpy_sum, python_sum],
    arguments={2**i: [random.randint(0, 10) for _ in range(2**i)] for i in range(2, 21)},
    argument_name='list size',
    function_aliases={numpy_sum: 'numpy.sum(<list>)', python_sum: "sum(<list>)"}
)

С этими результатами:

f, (ax1, ax2) = plt.subplots(1, 2, sharey=True)
b_array.plot(ax=ax1)
b_list.plot(ax=ax2)

enter image description here

Слева: на массиве NumPy; Справа: в списке Python. Обратите внимание, что это график log-log, поскольку эталонный тест охватывает очень широкий диапазон значений. Однако для качественных результатов: чем ниже, тем лучше.

Что показывает, что для списков sum Pythons всегда быстрее, в то время как np.sum или метод sum в массиве будут быстрее (за исключением очень коротких массивов, где sum Pythons быстрее).

На случай, если вы захотите сравнить их друг с другом, я также составил сюжет, включающий все из них:

f, ax = plt.subplots(1)
b_array.plot(ax=ax)
b_list.plot(ax=ax)
ax.grid(which='both')

enter image description here

Интересно, что точка, в которой numpy может конкурировать в массивах с Python и списками, составляет примерно 200 элементов! Обратите внимание, что это число может зависеть от множества факторов, таких как версия Python/NumPy,... Не воспринимайте это слишком буквально.

То, что не было упомянуто, является причиной этой разницы (я имею в виду большую разницу в масштабе, а не разницу для коротких списков/массивов, где функции просто имеют разные постоянные издержки). Предполагая, что CPython список Python является оберткой вокруг массива C (языка C) указателей на объекты Python (в данном случае это целые числа Python). Эти целые числа можно рассматривать как обертки вокруг целого числа C (не совсем корректно, поскольку целые числа Python могут быть сколь угодно большими, поэтому они не могут просто использовать одно целое число C, но достаточно близко).

Например, список типа [1, 2, 3] будет (схематично, я пропустил несколько деталей) храниться так:

enter image description here

Массив NumPy, однако, является оберткой вокруг массива C, содержащего значения C (в данном случае int или long зависимости от 32 или 64 бита и в зависимости от операционной системы).

Таким образом, массив NumPy, такой как np.array([1, 2, 3]) будет выглядеть так:

enter image description here

Следующее, что нужно понять, это как работают эти функции:

  • sum Питонов перебирает итерируемые (в данном случае список или массив) и добавляет все элементы.
  • NumPys sum метод перебирает хранимый массив C и добавляет эти значения C и, наконец, оборачивает это значение в Python типа (в данном случае numpy.int32 (или numpy.int64) и возвращает его.
  • NumPys sum функция преобразует вход на array (по крайней мере, если она не является массивом уже), а затем использует NumPy sum метод.

Ясно, что добавление значений C из массива C намного быстрее, чем добавление объектов Python, поэтому функции NumPy могут быть намного быстрее (см. Второй график выше, функции NumPy для массивов намного превосходят сумму Python для больших массивов).

Но преобразование списка Python в массив NumPy происходит относительно медленно, и тогда вам все равно придется добавить значения Си. Вот почему для списков sum Python будет быстрее.

Единственный оставшийся открытым вопрос - почему sum Питона на array такая медленная (это самая медленная из всех сравниваемых функций). И это на самом деле связано с тем фактом, что сумма Питона просто перебирает все, что вы передаете. В случае списка он получает сохраненный объект Python, но в случае массива 1D NumPy нет сохраненных объектов Python, только значения C, поэтому Python и NumPy должны создать объект Python (numpy.int32 или numpy.int64) для каждого элемента, а затем эти объекты Python должны быть добавлены. Создание оболочки для значения C - это то, что делает его действительно медленным.

Кроме того, каковы последствия (включая производительность) использования целого числа Python по сравнению со скалярным numpy.int32? Например, для + = 1, есть ли поведение или разница в производительности, если тип a представляет собой целое число Python или numpy.int32?

Я сделал несколько тестов, и для сложения и вычитания скаляров вы обязательно должны использовать целые числа Python. Несмотря на то, что может происходить некоторое кэширование, это означает, что следующие тесты могут быть не полностью репрезентативными:

from itertools import repeat

python_integer = 1000
numpy_integer_32 = np.int32(1000)
numpy_integer_64 = np.int64(1000)

def repeatedly_add_one(val):
    for _ in repeat(None, 100000):
        _ = val + 1

%timeit repeatedly_add_one(python_integer)
3.7 ms ± 71.2 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%timeit repeatedly_add_one(numpy_integer_32)
14.3 ms ± 162 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%timeit repeatedly_add_one(numpy_integer_64)
18.5 ms ± 494 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)


def repeatedly_sub_one(val):
    for _ in repeat(None, 100000):
        _ = val - 1

%timeit repeatedly_sub_one(python_integer)
3.75 ms ± 236 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
%timeit repeatedly_sub_one(numpy_integer_32)
15.7 ms ± 437 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
%timeit repeatedly_sub_one(numpy_integer_64)
19 ms ± 834 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Скалярные операции с целыми числами Python выполняются в 3-6 раз быстрее, чем со скалярами NumPy. Я не проверял, почему это так, но я предполагаю, что скаляры NumPy используются редко и, вероятно, не оптимизированы для производительности.

Разница становится немного меньше, если вы фактически выполняете арифметические операции, где оба операнда являются скалярными скалярами:

def repeatedly_add_one(val):
    one = type(val)(1)  # create a 1 with the same type as the input
    for _ in repeat(None, 100000):
        _ = val + one

%timeit repeatedly_add_one(python_integer)
3.88 ms ± 273 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
%timeit repeatedly_add_one(numpy_integer_32)
6.12 ms ± 324 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
%timeit repeatedly_add_one(numpy_integer_64)
6.49 ms ± 265 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Тогда это только в 2 раза медленнее.


В случае, если вам интересно, почему я использовал itertools.repeat здесь, когда я мог бы просто использовать вместо for _ in range(...). Причина в том, что repeat происходит быстрее и, следовательно, требует меньше накладных расходов за цикл. Поскольку меня интересует только время сложения/вычитания, на самом деле предпочтительнее не допускать, чтобы циклические накладные расходы влияли на время (по крайней мере, не так много).

Ответ 3

Numpy должен быть намного быстрее, особенно если ваши данные уже являются массивом numpy.

Массивы Numpy - это тонкий слой над стандартным массивом C. Когда numpy sum итерации над этим, он не делает проверку типов, и это очень быстро. Скорость должна быть сопоставимой с выполнением операции с использованием стандартного C.

Для сравнения, используя сумму python, он должен сначала преобразовать массив numpy в массив python, а затем перебрать этот массив. Он должен выполнять некоторую проверку типов и, как правило, будет медленнее.

Точная сумма, которую сумма python медленнее, чем numpy sum, не определена, поскольку сумма python будет несколько оптимизированной функцией по сравнению с написанием вашей собственной функции sum в python.

Ответ 4

Обратите внимание, что сумма Python на многомерных массивах numpy будет выполнять только сумму по первой оси:

sum(np.array([[[2,3,4],[4,5,6]],[[7,8,9],[10,11,12]]]))
Out[47]: 
array([[ 9, 11, 13],
       [14, 16, 18]])

np.sum(np.array([[[2,3,4],[4,5,6]],[[7,8,9],[10,11,12]]]), axis=0)
Out[48]: 
array([[ 9, 11, 13],
       [14, 16, 18]])

np.sum(np.array([[[2,3,4],[4,5,6]],[[7,8,9],[10,11,12]]]))
Out[49]: 81

Ответ 5

Это расширение для ответа на сообщение выше Akavall. Из этого ответа видно, что np.sum выполняет быстрее для объектов np.array, тогда как sum выполняет быстрее для объектов list. Чтобы расширить это:

При запуске np.sum для объекта np.array Vs. sum для объекта list, кажется, что они выполняют шею до шеи.

# I'm running IPython

In [1]: x = range(1000) # list object

In [2]: y = np.array(x) # np.array object

In [3]: %timeit sum(x)
100000 loops, best of 3: 14.1 µs per loop

In [4]: %timeit np.sum(y)
100000 loops, best of 3: 14.3 µs per loop

Выше, sum немного быстрее, чем np.array, хотя иногда я видел np.sum timings как 14.1 µs. Но в основном это 14.3 µs.