A tuple
zajmuje mniej miejsca w pamięci w Pythonie:
>>> a = (1,2,3)
>>> a.__sizeof__()
48
podczas gdy list
s zajmuje więcej miejsca w pamięci:
>>> b = [1,2,3]
>>> b.__sizeof__()
64
Co dzieje się wewnętrznie w zarządzaniu pamięcią w Pythonie?
A tuple
zajmuje mniej miejsca w pamięci w Pythonie:
>>> a = (1,2,3)
>>> a.__sizeof__()
48
podczas gdy list
s zajmuje więcej miejsca w pamięci:
>>> b = [1,2,3]
>>> b.__sizeof__()
64
Co dzieje się wewnętrznie w zarządzaniu pamięcią w Pythonie?
Odpowiedzi:
Zakładam, że używasz CPython i 64-bitowego (mam takie same wyniki na moim CPythonie 2.7 64-bitowym). Mogą występować różnice w innych implementacjach języka Python lub jeśli masz 32-bitowy język Python.
Niezależnie od implementacji,
list
s mają zmienną wielkość, atuple
s mają stałą wielkość.Więc
tuple
s może przechowywać elementy bezpośrednio w strukturze, z drugiej strony listy wymagają warstwy pośredniej (przechowuje wskaźnik do elementów). Ta warstwa pośrednia jest wskaźnikiem, w systemach 64-bitowych jest 64-bitowa, a więc 8-bajtowa.Ale jest jeszcze jedna rzecz, którą
list
robią: nadmiernie przydzielają. W przeciwnym razielist.append
byłaby toO(n)
operacja zawsze - aby ją zamortyzowaćO(1)
(znacznie szybciej !!!), nadmiernie alokuje. Ale teraz musi śledzić przydzielony rozmiar i wypełniony rozmiar (tuple
wystarczy przechowywać jeden rozmiar, ponieważ przydzielony i wypełniony rozmiar są zawsze identyczne). Oznacza to, że każda lista musi przechowywać inny „rozmiar”, który w systemach 64-bitowych jest 64-bitową liczbą całkowitą, ponownie 8 bajtów.Więc
list
s potrzebują co najmniej 16 bajtów więcej pamięci niżtuple
s. Dlaczego powiedziałem „przynajmniej”? Z powodu nadmiernej alokacji. Nadmierna alokacja oznacza, że przydziela więcej miejsca niż potrzeba. Jednak wielkość nadmiernej alokacji zależy od tego, „jak” utworzysz listę i historię dodawania / usuwania:Zdjęcia
Postanowiłem stworzyć obrazy towarzyszące powyższemu wyjaśnieniu. Może te są pomocne
Oto jak (schematycznie) jest on przechowywany w pamięci w twoim przykładzie. Podkreśliłem różnice z czerwonymi (z wolnej ręki) cyklami:
W rzeczywistości jest to tylko przybliżenie, ponieważ
int
obiekty są również obiektami Pythona, a CPython nawet ponownie wykorzystuje małe liczby całkowite, więc prawdopodobnie dokładniejsza reprezentacja (chociaż nie tak czytelna) obiektów w pamięci byłaby:Przydatne linki:
tuple
struct w repozytorium CPython dla Pythona 2.7list
struct w repozytorium CPython dla Pythona 2.7int
struct w repozytorium CPython dla Pythona 2.7Zwróć uwagę, że
__sizeof__
tak naprawdę nie zwraca „prawidłowego” rozmiaru! Zwraca tylko rozmiar przechowywanych wartości. Jednak gdy używasz,sys.getsizeof
wynik jest inny:Istnieją 24 „dodatkowe” bajty. Te są prawdziwe , to jest narzut modułu odśmiecania pamięci, który nie jest uwzględniony w
__sizeof__
metodzie. Dzieje się tak, ponieważ generalnie nie powinieneś używać magicznych metod bezpośrednio - użyj funkcji, które wiedzą, jak sobie z nimi poradzić, w tym przypadku:sys.getsizeof
(co faktycznie dodaje narzut GC do wartości zwracanej z__sizeof__
).źródło
list
alokacjilist()
lub rozumienia listy.Zagłębię się w podstawę kodu CPython, aby zobaczyć, jak obliczane są rozmiary. W swoim konkretnym przykładzie , nie nadmierne przydziały zostały wykonane, więc nie będę dotykać na tym .
Użyję tutaj wartości 64-bitowych, tak jak ty.
Rozmiar
list
s jest obliczany z następującej funkcjilist_sizeof
:Oto
Py_TYPE(self)
makro, które przechwytujeob_type
ofself
(zwracającePyList_Type
), podczas gdy_PyObject_SIZE
jest kolejnym makrem, które pobieratp_basicsize
z tego typu.tp_basicsize
jest obliczana jakosizeof(PyListObject)
gdziePyListObject
jest strukturą instancji.PyListObject
Struktura ma trzy pola:te mają komentarze (które przyciąłem) wyjaśniające, czym są, kliknij powyższy link, aby je przeczytać.
PyObject_VAR_HEAD
rozszerza się w trzech dziedzinach bajtowych (8ob_refcount
,ob_type
iob_size
) tak24
wkładu bajtów.Więc na razie
res
jest:lub:
Jeśli instancja listy zawiera przydzielone elementy. druga część oblicza ich wkład.
self->allocated
jak sama nazwa wskazuje, zawiera liczbę przydzielonych elementów.Bez żadnych elementów rozmiar list jest obliczany jako:
tj. rozmiar struktury instancji.
tuple
obiekty nie definiujątuple_sizeof
funkcji. Zamiast tego używająobject_sizeof
do obliczenia swojego rozmiaru:To, podobnie jak w przypadku
list
s, pobieratp_basicsize
i, jeśli obiekt ma wartośćtp_itemsize
różną od zera (co oznacza, że ma instancje o zmiennej długości), mnoży liczbę elementów w krotce (którą otrzymuje za pośrednictwemPy_SIZE
)tp_itemsize
.tp_basicsize
ponownie używa,sizeof(PyTupleObject)
gdziePyTupleObject
struktura zawiera :Więc bez żadnych elementów (czyli
Py_SIZE
zwraca0
) rozmiar pustych krotek jest równysizeof(PyTupleObject)
:co? Cóż, tutaj jest dziwactwo, dla którego nie znalazłem wyjaśnienia,
tp_basicsize
ztuple
s jest obliczane w następujący sposób:dlaczego
8
usuwane są dodatkowe bajty, totp_basicsize
jest coś, czego nie byłem w stanie znaleźć. (Zobacz komentarz MSeiferta w celu uzyskania możliwego wyjaśnienia)Ale to w zasadzie różnica w twoim konkretnym przykładzie .
list
s również trzymać wokół wielu przydzielonych elementów, co pomaga określić, kiedy ponownie należy nadmiernie alokować.Teraz, kiedy dodawane są dodatkowe elementy, listy rzeczywiście wykonują tę nadmierną alokację w celu osiągnięcia dołączeń O (1). Skutkuje to większymi rozmiarami, ponieważ MSeifert ładnie pokrywa się w jego odpowiedzi.
źródło
ob_item[1]
jest to głównie symbol zastępczy (więc ma sens, aby został odjęty od rozmiaru podstawowego).tuple
Przeznaczono użyciuPyObject_NewVar
. Nie rozgryzłem szczegółów, więc to tylko zgadywanie ...Odpowiedź MSeiferta obejmuje to szeroko; aby to uprościć, możesz pomyśleć o:
tuple
jest niezmienna. Po ustawieniu nie możesz go zmienić. Dzięki temu wiesz z góry, ile pamięci musisz przydzielić dla tego obiektu.list
jest zmienna. Możesz dodawać lub usuwać elementy do lub z niego. Musi znać jego rozmiar (dla celów wewnętrznych). W razie potrzeby zmienia rozmiar.Nie ma darmowych posiłków - te możliwości kosztują. Stąd nadmiar pamięci w przypadku list.
źródło
Rozmiar krotki jest poprzedzony prefiksem, co oznacza, że podczas inicjalizacji krotki interpreter przydziela wystarczającą ilość miejsca na zawarte dane, i to jest koniec tego, dając je niezmienne (nie można ich modyfikować), podczas gdy lista jest zmiennym obiektem, co oznacza dynamikę alokacja pamięci, aby uniknąć przydzielania miejsca za każdym razem, gdy dodajesz lub modyfikujesz listę (przydziel wystarczającą ilość miejsca na zmienione dane i skopiuj do niej dane), przydziela dodatkowe miejsce na przyszłe dodawanie, modyfikacje, ... to prawie podsumowuje.
źródło