Я ожидал, array.array
что будет быстрее, чем списки, так как массивы кажутся распакованными.
Однако я получаю следующий результат:
In [1]: import array
In [2]: L = list(range(100000000))
In [3]: A = array.array('l', range(100000000))
In [4]: %timeit sum(L)
1 loop, best of 3: 667 ms per loop
In [5]: %timeit sum(A)
1 loop, best of 3: 1.41 s per loop
In [6]: %timeit sum(L)
1 loop, best of 3: 627 ms per loop
In [7]: %timeit sum(A)
1 loop, best of 3: 1.39 s per loop
Что может быть причиной такой разницы?
python
arrays
performance
boxing
python-internals
Валентин Лоренц
источник
источник
array
пакет. Если вы хотите сделать значительный объем математики, Numpy работает со скоростью света (то есть C), и обычно лучше, чем наивные реализации таких вещей, какsum()
).array
довольно быстро конвертировать строку целых чисел (представляющих байты ASCII) вstr
объект. Сам Гвидо придумал это только после множества других решений и был весьма удивлен производительностью. Во всяком случае, это единственное место, где я помню, что это полезно.numpy
гораздо лучше работать с массивами, но это сторонняя зависимость.Ответы:
Хранения является «распакованный», но каждый раз , когда вы получаете доступ к элементу Python должен «поле» он (вставить его в обычный объект Python) для того , чтобы сделать что - нибудь с ним. Например, вы
sum(A)
перебираете массив и упаковываете каждое целое число, по одному, в обычныйint
объект Python . Это стоит времени. По вашемуsum(L)
, весь бокс был сделан во время создания списка.Итак, в конце концов, массив, как правило, медленнее, но требует значительно меньше памяти.
Вот соответствующий код из недавней версии Python 3, но те же основные идеи применимы ко всем реализациям CPython, так как Python был впервые выпущен.
Вот код для доступа к элементу списка:
Это очень мало:
somelist[i]
просто возвращаетi
ый объект в списке (и все объекты Python в CPython являются указателями на структуру, начальный сегмент которой соответствует макету astruct PyObject
).И вот
__getitem__
реализация дляarray
кода с типомl
:Необработанная память обрабатывается как вектор
C
long
целочисленных от платформы целых чисел;i
-мC long
читается до; и затемPyLong_FromLong()
вызывается для переноса («коробки») нативаC long
вlong
объекте Python (который в Python 3, который устраняет различие между Python 2 иint
andlong
, фактически отображается как типint
).Этот бокс должен выделить новую память для
int
объекта Python и добавитьC long
в него биты натива. В контексте исходного примера время жизни этого объекта очень короткое (достаточно длинное,sum()
чтобы добавить содержимое в промежуточный итог), и затем требуется больше времени для освобождения новогоint
объекта.Отсюда и разница в скорости, и всегда, и всегда в реализации CPython.
источник
Чтобы добавить к отличному ответу Тима Питерса, массивы реализуют протокол буфера , а списки - нет. Это означает, что если вы пишете расширение C (или моральный эквивалент, такой как написание модуля Cython ), то вы можете обращаться к элементам массива и работать с ними гораздо быстрее, чем все, что может сделать Python. Это даст вам значительное улучшение скорости, возможно, на порядок выше. Однако у него есть ряд недостатков:
В зависимости от вашего варианта использования, вы можете использовать кувалду, чтобы ударить муху. Сначала вы должны исследовать NumPy и посмотреть, достаточно ли он силен, чтобы делать то, что вы пытаетесь сделать. Это также будет намного быстрее, чем родной Python, если используется правильно.
источник
Тим Питерс ответил, почему это медленно, но давайте посмотрим, как это улучшить .
Придерживаясь вашего примера
sum(range(...))
(в 10 раз меньше, чем ваш пример, чтобы вписаться в память здесь):Таким образом, numpy также нужно блокировать / распаковывать, что имеет дополнительные накладные расходы. Чтобы сделать это быстро, нужно оставаться в коде NumPy C:
Таким образом, из списка решений для NumPy версии это фактор 16 во время выполнения.
Давайте также проверим, сколько времени занимает создание этих структур данных
Чистый победитель: Numpy
Также обратите внимание, что создание структуры данных занимает примерно столько же времени, сколько и суммирование, если не больше. Выделение памяти происходит медленно.
Использование памяти тех:
Таким образом, они занимают 8 байтов на число с различными издержками. Для диапазона, который мы используем, достаточно 32-битных, поэтому мы можем сохранить некоторую память.
Но оказывается, что добавление 64-битных целых на моей машине быстрее, чем 32-битных, поэтому это того стоит, только если вы ограничены памятью / пропускной способностью.
источник
пожалуйста, обратите внимание, что
100000000
равно10^8
не10^7
, и мои результаты, как следующие:источник