4 ответов:
я предполагаю, что вы используете CPython и с 64 битами (я получил те же результаты на моем CPython 2.7 64-бит). Там могут быть различия в других реализациях Python или если у вас 32-битный питон.
независимо от реализации,
lists имеют переменный размер, аtuples фиксированный размер.так
tupleможет хранить элементы непосредственно внутри структуры, списки на слой (он хранит указатель на элементы). Этот слой косвенность-это указатель, на 64-битных системах это 64bit, следовательно, 8bytes.но есть еще одна вещь, что
lists do: они чрезмерно выделяют. В противном случаеlist.appendбудетO(n)операция всегда - чтобы он амортизировалсяO(1)(гораздо быстрее!!!) его выделяет. Но теперь он должен следить за выделено размер и , заполненной размер (tuples нужно хранить только один размер, потому что выделенный и заполненный размер всегда идентичный.) Это означает, что каждый список должен хранить другой "размер", который на 64-битных системах является 64-битным целым числом, снова 8 байт.так
lists нужно по крайней мере на 16 байт больше памяти, чемtuples. Почему я сказал "по крайней мере"? Из-за перерасхода средств. Чрезмерное распределение означает, что он выделяет больше места, чем необходимо. Однако сумма перерасчета зависит от того, " как " вы создаете список и историю добавления/удаления:>>> l = [1,2,3] >>> l.__sizeof__() 64 >>> l.append(4) # triggers re-allocation (with over-allocation), because the original list is full >>> l.__sizeof__() 96 >>> l = [] >>> l.__sizeof__() 40 >>> l.append(1) # re-allocation with over-allocation >>> l.__sizeof__() 72 >>> l.append(2) # no re-alloc >>> l.append(3) # no re-alloc >>> l.__sizeof__() 72 >>> l.append(4) # still has room, so no over-allocation needed (yet) >>> l.__sizeof__() 72изображения
я решил создайте несколько изображений, чтобы сопровождать объяснение выше. Может быть, это полезно
вот как он (схематично) хранится в памяти в вашем примере. Я выделил различия с красными (свободными) циклами:
это на самом деле просто приближение, потому что
intобъекты также являются объектами Python, а CPython даже повторно использует небольшие целые числа, поэтому, вероятно, более точное представление (хотя и не такое читаемое) объекты в памяти будут:Полезные ссылки:
tupleструктура в репозитории CPython для Python 2.7listструктура в репозитории CPython для Python 2.7intструктура в репозитории CPython для Python 2.7отметим, что
__sizeof__на самом деле не возвращает "правильный" размер! Он только возвращает размер сохраненных значений. Однако, когда вы используетеsys.getsizeofрезультат разный:>>> import sys >>> l = [1,2,3] >>> t = (1, 2, 3) >>> sys.getsizeof(l) 88 >>> sys.getsizeof(t) 72есть 24 "лишних" байт. Это реальные, это накладные расходы сборщика мусора, которые не учитываются в
__sizeof__метод. Это потому, что вы обычно не должны использовать магические методы напрямую-используйте функции, которые знают, как с ними обращаться, в этом случае:sys.getsizeof(который на самом деле добавляет GC накладные расходы к значению, возвращенному из__sizeof__).
я сделаю более глубокое погружение в кодовую базу CPython, чтобы мы могли увидеть, как на самом деле рассчитываются размеры. в вашем конкретном примере,никаких перерасчетов не было выполнено, поэтому я не буду касаться этого.
я собираюсь использовать 64-битные значения здесь, как и вы.
размер
lists вычисляется из следующей функциейlist_sizeof:static PyObject * list_sizeof(PyListObject *self) { Py_ssize_t res; res = _PyObject_SIZE(Py_TYPE(self)) + self->allocated * sizeof(void*); return PyInt_FromSsize_t(res); }здесь
Py_TYPE(self)это макрос, который захватываетob_typeнаself(возвратPyList_Type), а_PyObject_SIZEэто еще один макрос, который захватываетtp_basicsizeиз этого типа.tp_basicsizeрассчитывается какsizeof(PyListObject)здесьPyListObject- это экземпляр структуры.The
PyListObjectструктура состоит из трех полей:PyObject_VAR_HEAD # 24 bytes PyObject **ob_item; # 8 bytes Py_ssize_t allocated; # 8 bytesу них есть комментарии (которые я обрезал), объясняющие, что это такое, перейдите по ссылке выше, чтобы прочитать их.
PyObject_VAR_HEADрасширяется на три 8 байт поля (ob_refcount,ob_typeиob_size) так24вклад байт.сейчас
res- это:sizeof(PyListObject) + self->allocated * sizeof(void*)или:
40 + self->allocated * sizeof(void*)если экземпляр списка содержит выделенные элементы. вторая часть вычисляет их вклад.
self->allocated, как это следует из названия, содержит количество выделенных элементов.без каких-либо элементов, размер списков рассчитывается следующим образом:
>>> [].__sizeof__() 40т. е. размер экземпляра структура.
tupleобъекты не определяют
ответ MSeifert охватывает его широко; чтобы он был простым, вы можете подумать:
tupleнезыблем. Как только он установлен, вы не можете изменить его. Таким образом, вы заранее знаете, сколько памяти вам нужно выделить для этого объекта.
listизменчиво. Вы можете добавлять или удалять элементы из него. Он должен знать его размер (для внутреннего impl.). Он изменяется по мере необходимости.нет бесплатного питания - эти возможности приходит с ценой. Следовательно, накладные расходы в памяти для списков.
размер кортежа имеет префикс, что означает, что при инициализации кортежа интерпретатор выделяет достаточно места для содержащихся в нем данных, и это конец, давая ему неизменяемость (не может быть изменен), тогда как список является изменяемым объектом, следовательно, подразумевающим динамическое выделение памяти, поэтому, чтобы избежать выделения пространства каждый раз, когда вы добавляете или изменяете список ( выделите достаточно места для хранения измененных данных и скопируйте данные в него), он выделяет дополнительное пространство для будущего добавления, модификации, ... это в значительной степени подводит итог.


Comments