Rozmiar listy w pamięci
Po prostu eksperymentowałem z rozmiarem struktur danych Pythona w pamięci. Napisałem następujący fragment:
import sys
lst1=[]
lst1.append(1)
lst2=[1]
print(sys.getsizeof(lst1), sys.getsizeof(lst2))
Testowałem kod na następujących konfiguracjach:
- Windows 7 64bit, Python3.1: wyjście jest:
52 40
więc lst1 ma 52 bajty, a lst2 ma 40 bajtów. - Ubuntu 11.4 32bit z Python3.2: wyjście jest
48 32
- Ubuntu 11.4 32bit Python2.7:
48 36
Czy ktoś może mi wyjaśnić, dlaczego te dwa rozmiary różnią się, chociaż oba są listami zawiera jedynkę?
W dokumentacji Pythona dla funkcji getsizeof znalazłem: ...adds an additional garbage collector overhead if the object is managed by the garbage collector.
czy może tak być w moim małym przykładzie?
3 answers
[24]} oto pełniejsza interaktywna sesja, która pomoże mi wyjaśnić, co się dzieje (Python 2.6 na Windows XP 32-bit, ale to naprawdę nie ma znaczenia): {]}
>>> import sys
>>> sys.getsizeof([])
36
>>> sys.getsizeof([1])
40
>>> lst = []
>>> lst.append(1)
>>> sys.getsizeof(lst)
52
>>>
Zauważ, że pusta lista jest nieco mniejsza niż ta z [1]
. Gdy element jest dołączany, jednak rośnie znacznie większy.
Objects/listobject.c
, w źródle CPython.
Pusta lista
Po utworzeniu pustej listy []
nie ma miejsca na elementy są przydzielane - widać to w PyList_New
. 36 bajtów to ilość miejsca wymaganego dla samej struktury danych listy na maszynie 32-bitowej.
Lista z jednym elementem
Podczas tworzenia listy z pojedynczym elementem [1]
, miejsce na jeden element jest przydzielane oprócz pamięci wymaganej przez samą strukturę danych listy. Ponownie można to znaleźć w PyList_New
. Biorąc pod uwagę size
jako argument, oblicza:
nbytes = size * sizeof(PyObject *);
A następnie ma:
if (size <= 0)
op->ob_item = NULL;
else {
op->ob_item = (PyObject **) PyMem_MALLOC(nbytes);
if (op->ob_item == NULL) {
Py_DECREF(op);
return PyErr_NoMemory();
}
memset(op->ob_item, 0, nbytes);
}
Py_SIZE(op) = size;
op->allocated = size;
Widzimy więc, że za pomocą size = 1
przydzielane jest miejsce na jeden wskaźnik. 4 bajty (na moim 32-bitowym pudełku).
Dodawanie do pustej listy
Podczas wywoływania append
na pustej liście, oto co się dzieje:
-
PyList_Append
rozmowyapp1
- W tym celu należy skontaktować się z Działem obsługi klienta.]}
-
app1
następnie wywołujelist_resize
zsize+1
(1 w naszym przypadku) -
list_resize
ma ciekawą strategię alokacji, podsumowaną w tym komentarzu ze źródła.
Here it jest:
/* This over-allocates proportional to the list size, making room
* for additional growth. The over-allocation is mild, but is
* enough to give linear-time amortized behavior over a long
* sequence of appends() in the presence of a poorly-performing
* system realloc().
* The growth pattern is: 0, 4, 8, 16, 25, 35, 46, 58, 72, 88, ...
*/
new_allocated = (newsize >> 3) + (newsize < 9 ? 3 : 6);
/* check for integer overflow */
if (new_allocated > PY_SIZE_MAX - newsize) {
PyErr_NoMemory();
return -1;
} else {
new_allocated += newsize;
}
Let ' s do some math
Zobaczmy, jak liczby, które cytowałem w sesji na początku mojego artykułu, są osiągane.
Tak więc 36 bajtów jest wielkością wymaganą przez samą strukturę danych listy na 32-bitach. W przypadku pojedynczego elementu miejsce jest przydzielane na jeden wskaźnik, czyli 4 dodatkowe bajty - łącznie 40 bajtów. Jak na razie ok.
Gdy app1
jest wywołana na pustej liście, wywołuje list_resize
z size=1
. Zgodnie z algorytmem over-allocation list_resize
, następny największy dostępny rozmiar po 1 to 4, więc miejsce na 4 wskaźniki zostanie przydzielone. 4 * 4 = 16 bajtów, a 36 + 16 = 52.
Rzeczywiście, wszystko ma sens: -)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-08-30 18:15:30
Przepraszam, poprzedni komentarz był trochę skrótowy.
Chodzi o to, że patrzysz na to, jak listy są przydzielane (i myślę, że może po prostu chciałeś zobaczyć, jak duże są rzeczy - w takim przypadku użyj sys.getsizeof()
)
Gdy coś zostanie dodane do listy, może się zdarzyć jedna z dwóch rzeczy:
-
Dodatkowy element mieści się w wolnej przestrzeni
Potrzebne jest dodatkowe miejsce, więc powstaje nowa lista, a zawartość kopiowana, a dodatkowa rzecz dodano.
Ponieważ (2) jest drogie (kopiowanie rzeczy, nawet wskaźników, zajmuje czas proporcjonalny do liczby rzeczy do skopiowania, więc rośnie w miarę powiększania się list) chcemy robić to rzadko. więc zamiast po prostu dodać trochę więcej miejsca, dodajemy cały kawałek. zazwyczaj wielkość dodawanej ilości jest podobna do tej, która jest już w użyciu - w ten sposób matematyka wychodzi na to, że średni koszt alokacji pamięci, rozłożony na wiele zastosowań, jest proporcjonalny tylko do listy rozmiar.
Więc to, co widzisz, jest związane z tym zachowaniem. nie znam dokładnych szczegółów, ale nie zdziwiłbym się, gdyby[]
lub [1]
(lub oba) były specjalnymi przypadkami, w których przydzielana jest tylko wystarczająca ilość pamięci (aby zapisać pamięć w tych typowych przypadkach), a następnie dodawanie "grab a new chunk" opisane powyżej dodaje więcej.
Ale nie znam dokĹ 'adnych szczegĂłĹ' Ăłw - tak w ogĂłle dziaĹ ' ajÄ ... tablice dynamiczne. dokładna implementacja list w Pythonie zostanie dopracowana tak, że jest optymalny dla typowych programów Pythona. tak naprawdę mówię tylko, że nie możesz ufać rozmiarowi listy, która powie Ci dokładnie, ile ona Zawiera - może zawierać dodatkową przestrzeń, a ilość dodatkowej wolnej przestrzeni jest trudna do oszacowania lub przewidzenia.
Ps ciekawą alternatywą jest tworzenie list jako Par (value, pointer)
, gdzie każdy wskaźnik wskazuje na następną krotkę. w ten sposób możesz powiększać listy stopniowo, chociaż całkowita używana pamięć jest wyższa. to jest lista powiązana (co python używa bardziej jak wektor lub tablica dynamiczna).
[]
jak i {[2] } są przydzielane dokładnie, ale to dołączenie do []
przydziela dodatkowy kawałek. komentarz w kodzie jest tym, co mówię powyżej (nazywa się to "nadmierną alokacją", a kwota jest proporcjonalna do tego, co mamy, tak że średni ("zamortyzowany") koszt jest proporcjonalny do wielkości).Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-08-30 18:02:51
Oto szybka demonstracja wzorca wzrostu listy. Zmiana trzeciego argumentu w funkcji range() zmieni wynik tak, aby nie wyglądał jak komentarze w listobject.c, ale wynik po prostu dołączając jeden element wydaje się być idealnie dokładne.
allocated = 0
for newsize in range(0,100,1):
if (allocated < newsize):
new_allocated = (newsize >> 3) + (3 if newsize < 9 else 6)
allocated = newsize + new_allocated;
print newsize, allocated
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-08-01 21:11:20