Большой объем памяти целых чисел по сравнению с результатом sys.getsizeof () - PullRequest
4 голосов
/ 09 апреля 2019

Python-Integer-объектам в диапазоне [1,2^30) требуется 28 байт, как указано в sys.getsizeof() и объяснено, например, в этой SO-записи .

Однако, когда я измеряю объем памяти с помощью следующего сценария:

#int_list.py:
import sys

N=int(sys.argv[1])
lst=[0]*N            # no overallocation

for i in range(N):
    lst[i]=1000+i    # ints not from integer pool

через

/usr/bin/time -fpeak_used_memory:%M python3 int_list.py <N>

Я получаю следующие пиковые значения памяти (Linux-x64, Python 3.6.2): ​​

   N     Peak memory in Kb        bytes/integer
-------------------------------------------   
   1            9220              
   1e7        404712                40.50 
   2e7        800612                40.52 
   3e7       1196204                40.52
   4e7       1591948                40.52

Таким образом, похоже, что 40.5 байтов необходимо для одного целочисленного объекта, то есть 12.5 байтов больше, чем выдано sys.getsizeof().

Дополнительные 8 байты легко объяснить - список lst не содержит целочисленных объектов, но ссылается на них - это означает, что требуется дополнительный указатель, то есть 8 байтов.

Однако как насчет других 4.5 байтов, для чего они используются?

Можно исключить следующие причины:

  • размер целочисленных объектов является переменным, но 10^7 меньше, чем 2^30, и, следовательно, все целое число будет 28 байт большим.
  • В списке нет перераспределения lst, что можно легко проверить с помощью sys.getsizeof(lst), что дает 8 умноженное на количество элементов плюс очень небольшие накладные расходы.

Ответы [ 2 ]

2 голосов
/ 11 апреля 2019

@ Предложение Натана, на удивление, не является решением из-за некоторых тонких деталей реализации longint в CPython.По его объяснению, объем памяти для

...
lst[i] = (1<<30)+i

все равно должен быть 40.52, потому что sys.sizeof(1<<30) равен 32, но измерения показывают, что он равен 48.56.С другой стороны, для

...
lst[i] = (1<<60)+i

след все еще равен 48.56, несмотря на то, что sys.sizeof(1<<60) равен 36.

Причина: sys.getsizeof() нене говорите нам реальный объем памяти для результата суммирования, то есть a+b, что составляет

  • 32 байта для 1000+i
  • 36 байтов для (1<<30)+i
  • 40 байтов для (1<<60)+i

Это происходит потому, что когда в x_add добавляются два целых числа, результирующее целое число имеет сначала одну "цифру", т.е.4 байта, больше, чем максимум a и b:

static PyLongObject *
x_add(PyLongObject *a, PyLongObject *b)
{
    Py_ssize_t size_a = Py_ABS(Py_SIZE(a)), size_b = Py_ABS(Py_SIZE(b));
    PyLongObject *z;
    ...
    /* Ensure a is the larger of the two: */
    ...
    z = _PyLong_New(size_a+1);  
    ...

после добавления, результат нормализован:

 ...
 return long_normalize(z);

};

т.е. возможные начальные нули отбрасываются, но память не освобождается - 4 байта не стоит, источник функции можно найти здесь .


Теперь мы можем использовать @Nathans insight, чтобы объяснить, почему (1<<30)+i занимает 48.56, а не 44.xy: используемый py_malloc -распределитель использует блоки памяти с выравниванием 8байты, это означает, что 36 байтов будет храниться в блоке размером 40 - то же самое, что и результат (1<<60)+i (учтите дополнительные 8 байтов для указателей).


Чтобы объяснить оставшиеся 0.5 байтов, нам нужно глубже погрузиться в детали py_malloc -allocator.Хороший обзор - сам исходный код , моя последняя попытка описать его можно найти в этом SO-post .

В двух словах, распределитель управляетПамять на аренах, каждый 256MB.Когда арена выделена, память резервируется, но не фиксируется.Мы видим память как «использованную», только когда происходит касание так называемого pool.Пул 4Kb большой (POOL_SIZE) и используется только для блоков памяти одинакового размера - в нашем случае 32 байт.Это означает, что разрешение peak_used_memory составляет 4 КБ и не может отвечать за эти 0.5 байтов.

Однако эти пулы должны управляться, что приводит к дополнительным издержкам: py_malloc требуется pool_header на пул:

/* Pool for small blocks. */
struct pool_header {
    union { block *_padding;
            uint count; } ref;          /* number of allocated blocks    */
    block *freeblock;                   /* pool's free list head         */
    struct pool_header *nextpool;       /* next pool of this size class  */
    struct pool_header *prevpool;       /* previous pool       ""        */
    uint arenaindex;                    /* index into arenas of base adr */
    uint szidx;                         /* block size class index        */
    uint nextoffset;                    /* bytes to virgin block         */
    uint maxnextoffset;                 /* largest valid nextoffset      */
};

Размер этой структуры составляет 48 (называемый POOL_OVERHEAD) байт на моем компьютере с Linux_64.Этот pool_header является частью пула (довольно разумный способ избежать дополнительного выделения через cruntime-memory-allocator) и будет состоять из двух 32 -byte-блоков, что означает, что пул имеет место для126 32 целых байтов :

/* Return total number of blocks in pool of size index I, as a uint. */
#define NUMBLOCKS(I) ((uint)(POOL_SIZE - POOL_OVERHEAD) / INDEX2SIZE(I))

Что приводит к:

  • 4Kb/126 = 32.51 байт для 1000+i, плюс дополнительные 8 байт дляуказатель.
  • (30<<1)+i требуется 40 байт, это означает, что 4Kb имеет место для 102 блоков, из которых один (оставшиеся 16 байтов, когда пул разделен на 40 -Блок байтов, и они могут быть использованы для pool_header) используется для pool_header, что приводит к 4Kb/101=40.55 байтов (плюс 8 указатель байтов).

Мы также можем увидеть, что есть некоторые дополнительные накладные расходы, ответственные за ок.0.01 байт на целое число - недостаточно большой для меня, чтобы заботиться.

2 голосов
/ 10 апреля 2019

Для объекта int требуется только 28 байтов, но Python использует 8-байтовое выравнивание: память выделяется в блоках, кратных размеру 8 байтов. Таким образом, фактическая память, используемая каждым int объектом, составляет 32 байта. См. Эту превосходную статью о управлении памятью Python для получения более подробной информации.

У меня пока нет объяснения оставшейся половины байта, но я обновлю его, если найду один.

...