Vraag Waarom hebben twee identieke lijsten een andere geheugenvoetafdruk?


Ik heb twee lijsten gemaakt l1 en l2, maar elk met een andere creatiemethode:

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

Maar de output verraste me:

Size of l1 = 144
Size of l2 = 192

De lijst met een lijstbegrip is groter in het geheugen, maar de twee lijsten zijn in Python anders hetzelfde.

Waarom is dat? Is dit een CPYthon intern ding of een andere verklaring?


112
2017-07-25 19:22


oorsprong


antwoorden:


Wanneer je schrijft [None] * 10, Python weet dat het een lijst met precies 10 objecten nodig heeft, dus het wijst precies dat toe.

Wanneer u een lijstbegrip gebruikt, weet Python niet hoeveel het nodig heeft. Dus groeit het geleidelijk aan de lijst als elementen worden toegevoegd. Voor elke herverdeling wijst het meer ruimte toe dan onmiddellijk nodig is, zodat het niet opnieuw toegewezen hoeft te worden voor elk element. De resulterende lijst is waarschijnlijk iets groter dan nodig.

U kunt dit gedrag zien wanneer u lijsten vergelijkt die zijn gemaakt met vergelijkbare formaten:

>>> sys.getsizeof([None]*15)
184
>>> sys.getsizeof([None]*16)
192
>>> sys.getsizeof([None for _ in range(15)])
192
>>> sys.getsizeof([None for _ in range(16)])
192
>>> sys.getsizeof([None for _ in range(17)])
264

Je kunt zien dat de eerste methode precies toewijst wat nodig is, terwijl de tweede methode periodiek groeit. In dit voorbeeld wordt voldoende voor 16 elementen toegewezen en moet opnieuw worden toegewezen wanneer de 17e wordt bereikt.


124
2017-07-25 19:31



Zoals opgemerkt in deze vraag het gebruik van lijstbegrip list.append onder de motorkap, dus het zal de lijst-resize-methode noemen, die overkoepelt.

Om dit voor jezelf te demonstreren, kun je de dis dissasembler:

>>> code = compile('[x for x in iterable]', '', 'eval')
>>> import dis
>>> dis.dis(code)
  1           0 LOAD_CONST               0 (<code object <listcomp> at 0x10560b810, file "", line 1>)
              2 LOAD_CONST               1 ('<listcomp>')
              4 MAKE_FUNCTION            0
              6 LOAD_NAME                0 (iterable)
              8 GET_ITER
             10 CALL_FUNCTION            1
             12 RETURN_VALUE

Disassembly of <code object <listcomp> at 0x10560b810, file "", line 1>:
  1           0 BUILD_LIST               0
              2 LOAD_FAST                0 (.0)
        >>    4 FOR_ITER                 8 (to 14)
              6 STORE_FAST               1 (x)
              8 LOAD_FAST                1 (x)
             10 LIST_APPEND              2
             12 JUMP_ABSOLUTE            4
        >>   14 RETURN_VALUE
>>>

Let op de LIST_APPEND opcode in de demontage van de <listcomp> code object. Van de docs:

LIST_APPEND (i) 

gesprekken list.append(TOS[-i], TOS). Gebruikt om lijstbegrippen te implementeren.

Nu, voor de lijstherhalingoperatie, hebben we een hint over wat er aan de hand is als we overwegen:

>>> import sys
>>> sys.getsizeof([])
64
>>> 8*10
80
>>> 64 + 80
144
>>> sys.getsizeof([None]*10)
144

Dus het lijkt te kunnen precies wijs de grootte toe. Kijken naar de broncode, we zien dat dit precies is wat er gebeurt:

static PyObject *
list_repeat(PyListObject *a, Py_ssize_t n)
{
    Py_ssize_t i, j;
    Py_ssize_t size;
    PyListObject *np;
    PyObject **p, **items;
    PyObject *elem;
    if (n < 0)
        n = 0;
    if (n > 0 && Py_SIZE(a) > PY_SSIZE_T_MAX / n)
        return PyErr_NoMemory();
    size = Py_SIZE(a) * n;
    if (size == 0)
        return PyList_New(0);
    np = (PyListObject *) PyList_New(size);

Namelijk hier: size = Py_SIZE(a) * n;. De rest van de functies vult eenvoudig de array.


38
2017-07-25 19:33



Geen is een blok geheugen, maar het is geen vooraf gespecificeerde grootte. Daarnaast is er extra ruimte in een array tussen array-elementen. Je kunt dit zelf zien door te draaien:

for ele in l2:
    print(sys.getsizeof(ele))

>>>>16
16
16
16
16
16
16
16
16
16

Wat niet de grootte van l2 is, maar eerder minder.

print(sys.getsizeof([None]))
72

En dit is veel groter dan een tiende van de grootte van l1.

Uw nummers moeten variëren, afhankelijk van zowel de details van uw besturingssysteem als de details van het huidige geheugengebruik in uw besturingssysteem. De grootte van [Geen] kan nooit groter zijn dan het beschikbare aangrenzende geheugen waarin de variabele is ingesteld om te worden opgeslagen en de variabele moet mogelijk worden verplaatst als deze later dynamisch groter wordt toegewezen.


3
2017-07-31 18:33