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?

Author: jonrsharpe, 2011-08-30

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.

[[24]}powodem tego są szczegóły implementacji w 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 rozmowy app1
  • W tym celu należy skontaktować się z Działem obsługi klienta.]}
  • app1 następnie wywołuje list_resize z size+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: -)

 91
Author: Eli Bendersky,
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:

  1. Dodatkowy element mieści się w wolnej przestrzeni

  2. 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).

[[7]} [Aktualizacja] zobacz doskonałą odpowiedź Eli. s / wyjaśnia, że zarówno [] 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).
 9
Author: andrew cooke,
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
 2
Author: Dean Stamler,
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