Utworzyłem dwie listy l1
i l2
, ale każda z inną metodą tworzenia:
import sys
l1 = [None] * 10
l2 = [None for _ in range(10)]
print('Size of l1 =', sys.getsizeof(l1))
print('Size of l2 =', sys.getsizeof(l2))
Ale wyjście mnie zaskoczyło:
Size of l1 = 144
Size of l2 = 192
Lista utworzona za pomocą funkcji list złożonych ma większy rozmiar w pamięci, ale w przeciwnym razie obie listy są identyczne w Pythonie.
Dlaczego? Czy to jakaś wewnętrzna sprawa CPythona, czy jakieś inne wyjaśnienie?
python
list
memory-management
python-internals
Andrej Kesely
źródło
źródło
144 == sys.getsizeof([]) + 8*10)
gdzie 8 to rozmiar wskaźnika.10
na11
,[None] * 11
lista będzie miała rozmiar152
, ale lista nadal będzie miała rozmiar192
. Wcześniej połączone pytanie nie jest dokładnym duplikatem, ale ma znaczenie dla zrozumienia, dlaczego tak się dzieje.Odpowiedzi:
Kiedy piszesz
[None] * 10
, Python wie, że będzie potrzebował listy dokładnie 10 obiektów, więc przydziela dokładnie to.Kiedy używasz rozumienia list, Python nie wie, ile będzie potrzebować. Tak więc lista stopniowo rośnie w miarę dodawania elementów. Przy każdej ponownej alokacji przydziela więcej miejsca, niż jest natychmiast potrzebne, więc nie musi ponownie przydzielać dla każdego elementu. Wynikowa lista prawdopodobnie będzie nieco większa niż potrzeba.
Możesz zobaczyć to zachowanie, porównując listy utworzone o podobnych rozmiarach:
Możesz zobaczyć, że pierwsza metoda przydziela tylko to, co jest potrzebne, podczas gdy druga rośnie okresowo. W tym przykładzie przydziela wystarczającą ilość na 16 elementów i musiał ponownie przydzielić po osiągnięciu 17.
źródło
*
gdy znam rozmiar z przodu.[x] * n
z niezmiennymix
na liście. Wynikowa lista będzie zawierała odniesienia do identycznego obiektu.Jak zauważono w tym pytaniu, rozumienie listy jest używane
list.append
pod maską, więc wywoła metodę zmiany rozmiaru listy, która z nadmierną alokacją.Aby zademonstrować to sobie, możesz faktycznie użyć
dis
dezasemblera:Zwróć uwagę na
LIST_APPEND
opcode w dezasemblacji<listcomp>
obiektu kodu. Z dokumentów :Teraz, jeśli chodzi o operację powtarzania listy, mamy wskazówkę dotyczącą tego, co się dzieje, jeśli weźmiemy pod uwagę:
Wydaje się więc, że jest w stanie dokładnie przydzielić rozmiar. Patrząc na kod źródłowy , widzimy dokładnie, co się dzieje:
Mianowicie tutaj:
size = Py_SIZE(a) * n;
. Reszta funkcji po prostu wypełnia tablicę.źródło
.extend()
.list.append
jest amortyzowaną operacją o stałym czasie, ponieważ zmiana rozmiaru listy powoduje nadmierną alokację. Dlatego nie każda operacja dołączania skutkuje nowo przydzieloną tablicą. W każdym razie kwestia, że związana pokazuje w kodzie źródłowym, że w rzeczywistości listowych zrobić stosowanielist.append
,. Zaraz wrócę do swojego laptopa i pokażę wam zdemontowany kod bajtowy dla zrozumienia listy i odpowiedniegoLIST_APPEND
koduŻaden jest blokiem pamięci, ale nie ma wstępnie określonego rozmiaru. Oprócz tego istnieje dodatkowe odstępy w tablicy między elementami tablicy. Możesz to zobaczyć, uruchamiając:
Co nie sumuje rozmiaru l2, ale raczej jest mniejsze.
A to znacznie więcej niż jedna dziesiąta rozmiaru
l1
.Twoje liczby powinny się różnić w zależności zarówno od szczegółów systemu operacyjnego, jak i szczegółów bieżącego wykorzystania pamięci w systemie operacyjnym. Rozmiar [None] nigdy nie może być większy niż dostępna sąsiednia pamięć, w której zmienna ma być przechowywana, a zmienna może wymagać przeniesienia, jeśli zostanie później przydzielona dynamicznie, aby była większa.
źródło
None
w rzeczywistości nie jest przechowywany w podstawowej tablicy, jedyną rzeczą, która jest przechowywana, jestPyObject
wskaźnik (8 bajtów). Wszystkie obiekty Pythona są przydzielane na stercie.None
jest singletonem, więc posiadanie listy z wieloma wartościami zerowymi po prostu utworzy tablicę wskaźników PyObject do tego samegoNone
obiektu na stercie (bez użycia dodatkowej pamięci w procesie na dodatkowyNone
). Nie jestem pewien, co masz na myśli, mówiąc „Żaden nie ma określonego rozmiaru”, ale to nie brzmi poprawnie. Wreszcie, twoja pętla zgetsizeof
każdym elementem nie demonstruje tego, co wydaje ci się, że demonstruje.gestsizeof
na każdymele
z nichl2
jest mylące, ponieważgetsizeof(l2)
nie uwzględnia rozmiaru elementów wewnątrz kontenera .l1 = [None]; l2 = [None]*100; l3 = [l2]
toprint(sys.getsizeof(l1), sys.getsizeof(l2), sys.getsizeof(l3))
. dostaniesz wynik takiego:72 864 72
. Oznacza to, odpowiednio64 + 1*8
,64 + 100*8
i64 + 1*8
, ponownie, zakładając system 64bit z 8 bajtów wielkości wskaźnika.sys.getsizeof
* nie uwzględnia rozmiaru przedmiotów w kontenerze. Z dokumentacji : „ Uwzględniane jest tylko zużycie pamięci bezpośrednio przypisane do obiektu, a nie zużycie pamięci przez obiekty, do których się ono odnosi ... Zobacz rekurencyjny przepis sizeof, aby zapoznać się z przykładem użycia metody getizeof () w celu znalezienia rozmiaru kontenerów i całą ich zawartość. "