Объем памяти в массиве Python по сравнению со списком - PullRequest
0 голосов
/ 24 апреля 2018

Я перелистываю книгу Свободный Питон. В нем говорится, что для последовательности всех чисел массив более эффективен и быстрее, чем список. Из того, что я понял из этого, у него также меньше памяти. В нем говорится «Массив Python столь же скуден, как массив C».

Мне любопытно, почему в массиве показывается больше памяти, чем в списке.

import array
from random import random
import sys

floats = array.array('d', (random() for i in range(10**7)))
L = [random() for i in range(10**7)]
print(sys.getsizeof(floats))
print(sys.getsizeof(L))

выход

81940352
81528056

Ответы [ 2 ]

0 голосов
/ 01 мая 2018

Извините, но я не думаю, что ответ @millimoose хорошо объясняет, что на самом деле происходит или что имел в виду автор, когда сказал, что массив быстрее списка.

Объем памяти:

Для дублирования требуется 8 байтов, и это именно то количество памяти, которое необходимо, если вы храните его в array - он хранится не как Python-Float, а каксырое 8-байтовое значение.Однако из-за перераспределения и некоторых дополнительных данных, сохраняемых в объекте массива, возникают небольшие накладные расходы (размер массива, размер буфера, тип значений в массиве и т. Д.).

A Python-Float требуется более 8 байтов:

>>> import sys
>>> f=1.0
>>> sys.getsizeof(f)
24

24 байта - довольно мало для Python-объекта!Например, обычному пустому объекту Python потребуется (Python3):

>>> class A:
      pass

>>> a=A()
>>> sys.getsizeof(a)
56

56 байт.Есть приемы, позволяющие уменьшить количество необходимых байтов, и все они используются для Python-Float, но вам все равно нужно 8 байтов для двойного значения, еще 8 байтов для счетчика ссылок и 8 байтов для указателя на описание типа.(таким образом, объект знает, что это объект Float).

Кроме того, в списке хранится не сам объект, а ссылка на него (т. е. указатель), для которого требуется 8 байтов.Таким образом, для сохранения Python-плавающего числа в списке необходимо 32 байта, что в 4 раза превышает объем используемой памяти.

Так почему же вы видите что-то другое при вызове sys.getsizeof для списка?Ответ: sys.getsizeof является не рекурсивным :

sys.getsizeof (object [, default])

....

Учитывается только потребление памяти , непосредственно приписываемое объекту, а не потребление памяти объектами, к которым он относится.

Это означает, что getsizeof для списка имеет значение толькопамять, необходимая для ссылок на объекты Float (8 байт на ссылку), а не размер объектов.Чтобы проиллюстрировать это:

>>> lst=[list(range(1000))]
>>> sys.getsizeof(lst)
72

Очевидно, что используется больше памяти, чем указано в 72 байтах.

Чтобы увидеть реальное потребление памяти, вам необходимо учитывать объем памяти, необходимый интерпретатору:

>>> /usr/bin/time -fpeak_used_memory:%M python -c "l=list((float(i) for i in range(10**7)))"
peak_used_memory:326832
>>> /usr/bin/time -fpeak_used_memory:%M python -c "import array; a=array.array('d',(float(i) for i in range(10**7)))"
peak_used_memory:88076

Как мы видим, разница (320 МБ против 80 МБ) примерно равна ожидаемому коэффициенту 4.

Скорость:

Автор не являетсясказав, что использование array.array с python-интерпретатором даст вам ускорение.Напротив, использование array.array с python-операциями замедлит работу, потому что сначала необработанные двойные значения должны быть преобразованы в Python-Floats:

lst=list(range(10**6))
%timeit sum(lst)
7.19 ms ± 461 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

import array
a=array.array('i',range(10**6))
%timeit sum(a)
17.9 ms ± 43.7 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

почти в 3 раза медленнее!

Тем не менее, есть потенциал, чтобы ускорить процесс - это не так просто.Для этого можно использовать NumPy, Cython или Numba.Например:

import numpy as np
b=np.array(range(10**6), dtype=np.int32)
%timeit b.sum()
1.07 ms ± 24.8 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

Почти в 10 раз быстрее!

0 голосов
/ 24 апреля 2018

Вы только что выбрали неправильный пример. Смысл использования array - это когда вам нужно хранить элементы, чье собственное представление меньше, чем у ссылки на объект Python. (Кажется, что здесь 8 байтов.) Например, если вы делаете:

from array import array
from os import urandom
a = array('B', urandom(1024))
l = list(a)
sys.getsizeof(a) # => 1155
sys.getsizeof(l) # => 9328

Поскольку double s также имеют ширину 8 байтов, на самом деле нет более компактного способа их хранения, чем другие 8 байтов.


Что касается остальных утверждений в книге, рассмотрите их с недоверием - вы не можете запускать код Python, то есть выполнять операции интерпретатором Python, и работать так же быстро, как и C. по-прежнему возникают накладные расходы при записи объектов Python в массив или чтении их из массива, что было бы быстрее, если бы выполнялась какая-то большая операция над всем массивом в нативной функции.

...