@ Предложение Натана, на удивление, не является решением из-за некоторых тонких деталей реализации 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
байт на целое число - недостаточно большой для меня, чтобы заботиться.