Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Применение памяти в Python

Anna | 16.06.2014 | нет комментариев

Сколько памяти занимает 1 миллион примитивных чисел?

Меня Зачастую донимали раздумье о том, насколько результативно Python использует память по сопоставлению с другими языками программирования. Скажем, сколько памяти необходимо, Дабы трудиться с 1 миллионом примитивных чисел? А с тем же числом строк произвольной длины?
Как оказалось, в Python есть вероятность получить нужную информацию прямо из интерактивной консоли, не обращаясь к начальному коду на C (правда, для верности, мы туда все таки заглянем).
Удовлетворив любопытство, мы залезем вовнутрь типов данных и узнаем, на что именно тратится память.

Все примеры были сделаны в CPython версии 2.7.4 на 32 битной машине. В конце приведена таблица для спросы в памяти на 64 битной машине.

Нужные инструменты

sys.getsizeof и способ __sizeof__()

1-й инструмент, тот, что нам понадобится находится в стандартной библиотеки sys. Цитируем официальную документацию:

sys.getsizeof(объект[, значение_по_умолчанию])

Возвращает размер объекта в байтах.
Если указано значение по умолчанию, то оно вернется, если объект не предоставляет метода получить размер. В отвратном случае возникнет исключение TypeError.
Getsizeof() вызывает способ объекта __sizeof__ и добавляет дополнительную информацию, которая храниться для сборщика мусора, если он применяется.

Алгорифм работы getsizeof(), переписанной на Python, мог бы выглядеть дальнейшем образом:

Py_TPFLAGS_HAVE_GC = 1 << 14  # константа. в двоичным виде равно 0b100000000000000
def sys_getsizeof(obj, default = None)        
    if obj.hasattr('__sizeof__'):
        size = obj.__sizeof__()
    elif default is not None:
        return default
    else:
        raise TypeError('Объект не имеет признака __sizeof__')
    # Если у типа объекта установлен флаг HAVE_GC
    if type(obj).__flags__ & Py_TPFLAGS_HAVE_GC:
        size = size   размер PyGC_Head
    return size

Где PyGC_Head — элемент двойного связанного списка, тот, что применяется сборщиком мусора для выявления кольцевых ссылок. В начальным коде он представлен дальнейшей конструкцией:

typedef union _gc_head {
    struct {
        union _gc_head *gc_next;
        union _gc_head *gc_sourcev;
        Py_ssize_t gc_refs;
    } gc;
    long double dummy;  
} PyGC_Head;

Размер PyGC_Head будет равен 12 байт на 32 битной и 24 байта на 64 битной машине.

Испробуем вызвать getsizeof() в консоле и посмотрим, что получится:

>>> import sys 
>>> GC_FLAG = 1 << 14
>>> sys.getsizeof(1) 
12 
>>> (1).__sizeof__()
12
>>> bool(type(1).__flags__ & GC_FLAG)
False
>>> sys.getsizeof(1.1) 
16 
>>> (1.1).__sizeof__()
16
>>> bool(type(1.1).__flags__ & GC_FLAG)
False
>>> sys.getsizeof('') 
21 
>>> ''.__sizeof__()
21
>>> bool(type('').__flags__ & GC_FLAG)
False
>>> sys.getsizeof('hello') 
26 
>>> sys.getsizeof(tuple()) 
24
>>> tuple().__sizeof__()
12
>>> bool(type(tuple()).__flags__ & GC_FLAG)
True
>>> sys.getsizeof(tuple((1, 2, 3))) 
36

За исключением магии с проверкой флагов, все дюже легко.
Как видно из примера, int и float занимают 12 и 16 байт соответственно. Str занимает 21 байт и еще по одному байту на всякий символ содержимого. Пустой кортеж занимает 12 байт, и добавочно 4 байта на всякий элемент. Для примитивных типов данных (которые не содержат ссылок на другие объекты, и соответственно, не отслеживаются сборщиком мусора), значение sys.getsizeof равно значению, возвращаемого способом __sizeof__().

id() и ctypes.string_at

Сейчас узнаем, на что именно тратится память.
Для этого необходимо нам необходимы две вещи: во-1-й узнать, где именно хранится объект, а во-вторых получить прямой доступ на чтения из памяти. Не смотря на то что Python скрупулезно оберегает нас от прямого обращения к памяти, этосделать все таки допустимо. При этом необходимо быть осмотрительным, так как неправильное применение может привести к ошибке сегментирования.

Встроенная фунция id() возвращает адрес памяти, где храниться начала объекта (сам объект является C конструкцией)

>>> obj = 1 
>>> id(obj) 
158020320

Дабы считать данные по адресу памяти необходимо воспользоваться функцией string_at из модуля ctypes. Ее официальное изложение не дюже подробное:

ctypes.string_at(адрес[, длина])
Это функция возвращает строку, с началом в ячейки памяти «адрес». Если «длина» не указана, то считается что строка zero-terminated,

Сейчас испробуем считать данные по адресу, тот, что вернул нам id():

>>> import ctypes 
>>> obj = 1 
>>> sys.getsizeof(obj)
12
>>> ctypes.string_at(id(obj), 12) 
'ux01x00x00 xf2&x08x01x00x00x003x01x00x00 xf2&x08x00x00x00x001x00x00x00' 

Вид шестнадцатеричного кода не дюже впечатляет, но мы близки к истине.

Модель Struct

Для того Дабы представить итог в значения, комфортные для воспринятия, воспользуемся еще одним модулем. Тут нам поможет функция unpack() из модуля struct.

struct
Данный модуль изготавливает реформирование между значениями Python и конструкциями на C, представленными в виде строк.

struct.unpack(формат, строка)
Разбирает строку в соответствие с данным форматов. Неизменно возвращает кортеж, даже если строка содержит только один элемент. Строка должна содержать в точности число информации, как описано форматом.

Форматы данных, которые нам понадобятся.

символ Значение C Значение Python Длина на 32битной машине
c char Строка из одного символа 1
i int int 4
l long int 4
L unsigned long int 4
d double float 8

Сейчас собираем все совместно и посмотрим на морально устройство некоторых типов данных.

Int
>>> obj = 1
>>> sys.getsizeof(obj), obj.__sizeof__() 
(12, 12) 
>>> struct.unpack('LLl', ctypes.string_at(id(obj), 12)) 
(373, 136770080, 1) 

О формате значений нетрудно додуматься.

Первое число (373) — число указателей, на объект.

>>> obj2 = obj
>>> struct.unpack('LLl', ctypes.string_at(id(obj), 12)) 
(374, 136770080, 1) 

Как видим, число увеличилось на единицу, позже того как мы сделали еще одну ссылку на объект.

Второе число (136770080) — указатель (id) на тип объекта:

>>> type(obj) 
<type 'int'>
>>> id(type(obj) )
136770080

Третье число (1) — непринужденно содержимое объекта.

>>> obj = 1234567 
>>> struct.unpack('LLl', ctypes.string_at(id(obj), 12)) 
(1, 136770080, 1234567)

Наши гипотезы дозволено удостоверить, заглянув в начальный код CPython

typedef struct { 
    PyObject_HEAD 
    long ob_ival; 
} PyIntObject;

Тут PyObject_HEAD — макрос, всеобщий для всех встроенных объектов, а ob_ival — значение типа long. Макрос PyObject_HEAD добавляет счетчик числа указателей на объект и указатель на родительский тип объекта — как раз то, что мы и видили.

Float

Число с плавающей запятой дюже схоже на int, но представлено в памяти C значением типа double.

typedef struct { 
    PyObject_HEAD 
    double ob_fval; 
} PyFloatObject;

В этом легко убедится:

>>> obj = 1.1 
>>> sys.getsizeof(obj), obj.__sizeof__() 
(16, 16) 
>>> struct.unpack('LLd', ctypes.string_at(id(obj), 16) 
(1, 136763968, 1.1) 
Строка (Str)

Строка представлена в виде массива символов, оканчивающийся нулевым байтом. Также в структуре строки отдельного сохраняется ее длина, хэш от нее оглавления и флаг, определяющий, хранится ли она во внутреннем кэше interned.

typedef struct { 
    PyObject_VAR_HEAD 
    long ob_shash;   # хэш от строки
    int ob_sstate;  # находится ли в кэше?
    char ob_sval[1];  # содержимое строки    нулевой байт
} PyStringObject;

Макрос PyObject_VAR_HEAD включает в себя PyObject_HEAD и добавляет значение long ob_ival, в котором хранится длина строки.

>>> obj = 'hello world' 
>>> sys.getsizeof(obj), obj.__sizeof__() 
(32, 32) 
>>> struct.unpack('LLLli'   'c' * (len(obj)   1), ctypes.string_at(id(obj), 4*5   len(obj)   1)) 
(1, 136790112, 11, -1500746465, 0, 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', 'x00') 

Четвертое значение соответствуют хэшу от строки, в чем не сложно убедится.

>>> hash(obj) 
-1500746465 

Как видимо, значение sstate равно 0, так что строка теперь не кэшируется. Испробуем ее добавить в кэш:

>>> intern(obj) 
'hello world' 
>>> struct.unpack('LLLli'   'c' * (len(obj)   1), ctypes.string_at(id(obj), 4*5   len(obj)   1)) 
(2, 136790112, 11, -1500746465, 1, 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', 'x00') 
Кортеж (Tuple)

Кортеж представлен в виде массива из указателей. Так как его применение может приводить к появлению кольцевых ссылок, он отслеживается сборщиком мусора, на что тратится добавочная память (об этом нам напоминает вызов sys.getsizeof())

Конструкция tuple схоже на строку, только в ней отсутствуют особые поля, помимо длины.

typedef struct { 
    PyObject_VAR_HEAD 
    PyObject *ob_item[1]; 
} PyTupleObject;
>>> obj = (1,2,3)
>>> sys.getsizeof(obj), obj.__sizeof__()
(36, 24)
>>> struct.unpack('LLL' 'L'*len(obj), ctypes.string_at(id(obj), 12 4*len(obj)))
(1, 136532800, 3, 146763112, 146763100, 146763088)
>>> for i in obj: print i, id(i)
1 146763112
2 146763100
3 146763088

Как видим из пример, последние три элементы кортежа являются указателями на его содержимое.

Остальные базовые типы данных (unicode, list, dict, set, frozenset) дозволено изучать аналогичным образом.

Что в результате?

Тип Имя в CPython формат Формат, для вложенных объектов Длина на 32bit Длина на 64bit Память для GC*
Int PyIntObject LLl 12 24
float PyFloatObject LLd 16 24
str PyStringObject LLLli c*(длина 1) 21 длина 37 длина
unicode PyUnicodeObject LLLLlL L*(длина 1) 28 4*длина 52 4*длина
tuple PyTupleObject LLL L*длина 12 4*длина 24 8*длина Есть
list PyListObject L*5 L*длину 20 4*длина 40 8*длина Есть
Set/
frozenset
PySetObject L*7 (lL)*8 lL LL* длина (<=5 элементов) 100
(>5 элементов) 100 8*длина
(<=5 элементов) 200
(>5 элементов) 200 16*длина
Есть
dict PyDictObject L*7 (lLL)*8 lLL*длина (<=5 элементов) 124
(>5 элементов) 124 12*длина
(<=5 элементов) 248
(>5 элементов) 248 24*длина
Есть

* Добавлять 12 байт на 32 битной машине и 32 байта на 64 битной машине

Мы видим, что примитивные типы данных в Python в два-три раза огромнее своих прототипов на C. Разница обусловлена необходимостью беречь число ссылок на объект и указатель на его тип (содержимое макроса PyObject_HEAD). Отчасти это компенсируется внутреннем кэширование, тот, что разрешает вторично применять ранее сделанные объекты (это допустимо только для неизменнымых типов).

Для строк и кортежей разница не такая существенная — добавляется некоторая непрерывная величина.

А списки, словари и множества, как правило, занимают огромнее на 1/3, чем нужно. Это обусловлено реализацией алгорифмов добавления новых элементов, тот, что приносит в жертву память раде экономии времени процессора.

Выходит, отвечаем на вопрос в начале статьи: Дабы сберечь 1 миллион примитивных чисел нам понадобится 11.4 мегабайт (12*10^6 байт) на сами числа и добавочно 3.8 мегабайт (12 4 4*10^6 байт) на кортеж, которых будет беречь на них ссылки.

 

Источник: programmingmaster.ru

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB