A tuple
занимает меньше места в памяти в Python:
>>> a = (1,2,3)
>>> a.__sizeof__()
48
тогда как list
s занимает больше места в памяти:
>>> b = [1,2,3]
>>> b.__sizeof__()
64
Что происходит внутри управления памятью Python?
A tuple
занимает меньше места в памяти в Python:
>>> a = (1,2,3)
>>> a.__sizeof__()
48
тогда как list
s занимает больше места в памяти:
>>> b = [1,2,3]
>>> b.__sizeof__()
64
Что происходит внутри управления памятью Python?
Ответы:
Я предполагаю, что вы используете CPython и 64-битную версию (я получил те же результаты на моем 64-битном CPython 2.7). Могут быть различия в других реализациях Python или если у вас 32-битный Python.
Независимо от реализации,
list
s имеют переменный размер, аtuple
s - фиксированный.Таким образом,
tuple
s может хранить элементы непосредственно внутри структуры, с другой стороны, спискам нужен уровень косвенности (он хранит указатель на элементы). Этот уровень косвенного обращения представляет собой указатель, в 64-битных системах это 64-битный, следовательно, 8 байт.Но есть еще одна вещь, которую можно
list
сделать: они перераспределяют ресурсы. В противном случаеlist.append
это была быO(n)
операция всегда - чтобы амортизироватьO(1)
(намного быстрее !!!), он перераспределяет. Но теперь он должен отслеживать выделенный размер и заполненный размер (tuple
нужно хранить только один размер, потому что выделенный и заполненный размер всегда идентичны). Это означает, что каждый список должен хранить другой «размер», который в 64-битных системах представляет собой 64-битное целое число, опять же 8 байтов.Таким образом,
list
s требуется как минимум на 16 байт памяти больше, чемtuple
s. Почему я сказал «хотя бы»? Из-за перераспределения. Избыточное выделение означает, что он выделяет больше места, чем необходимо. Однако величина перераспределения зависит от того, «как» вы создаете список и историю добавления / удаления:Изображений
Я решил создать несколько изображений, чтобы дополнить объяснение выше. Может быть, это полезно
Вот как он (схематично) хранится в памяти в вашем примере. Я выделил различия красными (произвольными) циклами:
На самом деле это всего лишь приближение, потому что
int
объекты также являются объектами Python, а CPython даже повторно использует небольшие целые числа, поэтому, вероятно, более точное представление (хотя и не столь читаемое) объектов в памяти будет:Полезные ссылки:
tuple
struct в репозитории CPython для Python 2.7list
struct в репозитории CPython для Python 2.7int
struct в репозитории CPython для Python 2.7Обратите внимание, что на
__sizeof__
самом деле не возвращается "правильный" размер! Он возвращает только размер сохраненных значений. Однако при использованииsys.getsizeof
результат будет другим:Есть 24 «лишних» байта. Это настоящие накладные расходы сборщика мусора, которые не учитываются в
__sizeof__
методе. Это потому, что обычно вы не должны использовать магические методы напрямую - используйте функции, которые знают, как их обрабатывать, в этом случае:sys.getsizeof
(что фактически добавляет накладные расходы GC к значению, возвращаемому из__sizeof__
).источник
list
распределении памяти stackoverflow.com/questions/40018398/…list()
или понимании списка.Я углублюсь в кодовую базу CPython, чтобы мы могли увидеть, как на самом деле рассчитываются размеры. Не в вашем конкретном примере , не более-распределения были выполнены, поэтому я не буду касаться что .
Я собираюсь использовать здесь 64-битные значения, как и вы.
Размер
list
s рассчитывается по следующей функцииlist_sizeof
:Вот
Py_TYPE(self)
макрос , который захватываетob_type
изself
(возвращениеPyList_Type
) в то время как_PyObject_SIZE
еще один макрос , который захватываетtp_basicsize
от этого типа.tp_basicsize
вычисляется какsizeof(PyListObject)
гдеPyListObject
находится структура экземпляра.В
PyListObject
структуре есть три поля:у них есть комментарии (которые я обрезал), объясняющие, что они собой представляют, перейдите по ссылке выше, чтобы прочитать их.
PyObject_VAR_HEAD
расширяется в три 8-байтовых поля (ob_refcount
,ob_type
иob_size
), так что24
байтовый вклад.Итак,
res
пока:или:
Если в экземпляре списка есть выделенные элементы. вторая часть подсчитывает их вклад.
self->allocated
, как следует из названия, содержит количество выделенных элементов.Без каких-либо элементов размер списков рассчитывается следующим образом:
т.е. размер структуры экземпляра.
tuple
объекты не определяютtuple_sizeof
функцию. Вместо этого они используютobject_sizeof
для расчета своего размера:Это, как и
list
s, захватываетtp_basicsize
и, если объект имеет ненулевоеtp_itemsize
значение (что означает, что у него есть экземпляры переменной длины), он умножает количество элементов в кортеже (которые он получает черезPy_SIZE
)tp_itemsize
.tp_basicsize
снова использует,sizeof(PyTupleObject)
гдеPyTupleObject
структура содержит :Итак, без каких-либо элементов (то есть
Py_SIZE
возвратов0
) размер пустых кортежей равенsizeof(PyTupleObject)
:а? Что ж, вот странность, объяснения которой я не нашел, на самом деле
tp_basicsize
oftuple
s вычисляется следующим образом:почему
8
удаляются дополнительные байты,tp_basicsize
мне не удалось выяснить. (См. Комментарий MSeifert для возможного объяснения)Но это в основном разница в вашем конкретном примере .
list
s также поддерживает некоторое количество выделенных элементов, что помогает определить, когда снова следует перераспределить.Теперь, когда добавляются дополнительные элементы, списки действительно выполняют это избыточное выделение для достижения O (1) добавлений. Это приводит к большим размерам, поскольку MSeifert прекрасно покрывает свой ответ.
источник
ob_item[1]
это в основном заполнитель (поэтому имеет смысл вычесть его из основного размера).tuple
Выделяется использованиемPyObject_NewVar
. Я не разобрался в деталях, так что это простоОтвет MSeifert охватывает это широко; для простоты вы можете подумать о:
tuple
неизменен. Как только он установлен, вы не можете его изменить. Таким образом, вы заранее знаете, сколько памяти вам нужно выделить для этого объекта.list
изменчив. Вы можете добавлять или удалять элементы в нем или из него. Он должен знать его размер (для внутренних имп.). Его размер изменяется по мере необходимости.Бесплатного питания нет - за эти возможности приходится платить. Отсюда накладные расходы на память для списков.
источник
Размер кортежа имеет префикс, что означает, что при инициализации кортежа интерпретатор выделяет достаточно места для содержащихся данных, и это его конец, что делает его неизменным (не может быть изменен), тогда как список является изменяемым объектом, следовательно, подразумевает динамический выделение памяти, поэтому, чтобы не выделять место каждый раз, когда вы добавляете или изменяете список (выделяете достаточно места для хранения измененных данных и копируете в него данные), он выделяет дополнительное пространство для будущего добавления, изменений, ... что в значительной степени подводит итог.
источник