A tupleзаймає менше пам’яті в Python:
>>> a = (1,2,3)
>>> a.__sizeof__()
48
тоді як lists займає більше місця в пам'яті:
>>> b = [1,2,3]
>>> b.__sizeof__()
64
Що відбувається всередині управління пам'яттю Python?
A tupleзаймає менше пам’яті в Python:
>>> a = (1,2,3)
>>> a.__sizeof__()
48
тоді як lists займає більше місця в пам'яті:
>>> b = [1,2,3]
>>> b.__sizeof__()
64
Що відбувається всередині управління пам'яттю Python?
Відповіді:
Я припускаю, що ви використовуєте CPython та 64-бітові (я отримав однакові результати на своєму 64-бітному CPython 2.7). Можуть бути відмінності в інших реалізаціях Python або якщо у вас 32-бітний Python.
Незалежно від реалізації, lists мають змінний розмір, а tuples мають фіксований розмір.
Так tuples може зберігати елементи безпосередньо всередині структури, списки з іншого боку потребують шару непрямості (він зберігає вказівник на елементи). Цей шар непрямості є вказівником для 64-бітових систем, які мають 64 біт, отже, 8 байт.
Але є ще одна річ, яка listробиться: вони перерозподіляють. В іншому випадку list.appendбуло б в O(n)операції завжди - щоб зробити його амортизуєтьсяO(1) (набагато швидше !!!) це по-Розподіляє. Але тепер він повинен відслідковувати виділений розмір і заповнений розмір ( tuples потрібно зберігати лише один розмір, оскільки розміщений і заповнений розмір завжди однаковий). Це означає, що кожен список повинен зберігати ще один "розмір", який у 64-бітових системах - це 64-бітове ціле число, знову ж 8 байт.
Так list s потрібно як мінімум на 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__).
listвиділення пам'яті stackoverflow.com/questions/40018398 / ...
list()або розумінні списку.
Я заглиблююсь у кодову базу 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структура екземпляра.
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 об'єкти не визначають a tuple_sizeof функцію. Натомість вони використовують object_sizeofдля обчислення їх розміру:
static PyObject *
object_sizeof(PyObject *self, PyObject *args)
{
Py_ssize_t res, isize;
res = 0;
isize = self->ob_type->tp_itemsize;
if (isize > 0)
res = Py_SIZE(self) * isize;
res += self->ob_type->tp_basicsize;
return PyInt_FromSsize_t(res);
}
Це, як і для lists, захоплює tp_basicsizeі, якщо об'єкт не має нуляtp_itemsize (тобто має екземпляри змінної довжини), він помножує кількість елементів у кортежі (через який він отримує Py_SIZE) зtp_itemsize .
tp_basicsizeзнову використовує sizeof(PyTupleObject)де PyTupleObject структура містить :
PyObject_VAR_HEAD # 24 bytes
PyObject *ob_item[1]; # 8 bytes
Отже, без будь-яких елементів (тобто Py_SIZEповертається0 ) розмір порожніх кортежів дорівнює sizeof(PyTupleObject):
>>> ().__sizeof__()
24
так? Ну ось ось дивацтво, якому я не знайшов поясненняtp_basicsize з tupleх фактично розраховується наступним чином :
sizeof(PyTupleObject) - sizeof(PyObject *)
чому додатковий 8 видаляється байт - tp_basicsizeце те, чого я не зміг з’ясувати. (Дивіться коментар MSeifert для можливого пояснення)
Але, це в основному різниця у вашому конкретному прикладі .lists також тримати навколо декількох виділених елементів, що допомагає визначити, коли знову перерозподілити.
Тепер, коли додаються додаткові елементи, списки справді виконують це перерозподіл, щоб отримати додатки O (1). Це призводить до більших розмірів, оскільки MSeifert чудово висвітлює свою відповідь.
ob_item[1]здебільшого це заповнювач (тому є сенс, що його відняти від основної величини). tupleВиділяється використанням PyObject_NewVar. Я не з'ясував деталей, тому це лише здогадка про здогадку ...
Відповідь MSeifert широко висвітлює її; щоб це було просто, ви можете придумати:
tupleнезмінна. Після встановлення його ви не можете змінити. Тож ви заздалегідь знаєте, скільки пам’яті потрібно виділити для цього об’єкта.
listє змінним. Ви можете додавати або видаляти елементи з нього. Він повинен знати його розмір (для внутрішніх імп.). Він змінює розмір за потребою.
Безкоштовного харчування немає - ці можливості поставляються із вартістю. Звідси накладні витрати на пам'ять для списків.
Розмір кортежу є префіксом, тобто при ініціалізації кортежу інтерпретатор виділяє достатньо місця для вміщених даних, і це є кінцем, даючи це незмінне (неможливо змінити), тоді як список є об'єктом, що змінюється, що означає динаміку Виділення пам'яті, щоб уникнути виділення місця кожного разу, коли ви додаєте або змінюєте список (виділіть достатньо місця для вмісту змінених даних та копіювання даних до нього), він виділяє додатковий простір для майбутнього додавання, модифікацій, ... що дуже багато резюмує це