То, что вы наблюдаете, - это не стратегия CPython, а стратегия распределителя памяти, которая поставляется вместе со средой выполнения C, используемой вашей CPython-версией.
Когда CPython выделяет / освобождает память через malloc/free
,он не взаимодействует напрямую с базовой ОС, а с конкретной реализацией распределителя памяти.В моем случае в Linux это Распределитель GNU .
Распределитель GNU имеет разные так называемые арены, где память не возвращается в ОС, но сохраняется, чтобы ее можно было использовать повторно.без необходимости общаться с ОС.Однако, если запрашивается большой объем памяти (независимо от определения «большой»), распределитель не использует память из арен, а запрашивает память из ОС и, как следствие, может вернуть ее непосредственно ОС, один раз free
называется.
CPython имеет свой собственный распределитель памяти - pymalloc , который построен поверх C-runtime-allocator.Оптимизирован для небольших объектов, которые живут на специальной арене;при создании / освобождении этих объектов меньше затрат по сравнению с базовым C-runtime-allocator.Однако объекты размером более 512 байт не используют эту арену, а управляются непосредственно C-runtime-allocator.
Ситуация еще более сложна с массивом numpy, поскольку дляметаданные (например, форма, тип данных и другие флаги) и для самих фактических данных:
- Для метаданных
PyArray_malloc
, распределитель памяти CPython (т.е.pymalloc). - Для самих данных используется
PyDataMem_NEW
, который напрямую использует базовую функциональность C-runtimme:
NPY_NO_EXPORT void *
PyDataMem_NEW(size_t size)
{
void *result;
result = malloc(size);
...
return result;
}
Я не уверен, какова была точная идея этого дизайна: очевидно, что хотелось бы воспользоваться преимуществами оптимизации небольших объектов pymalloc, и для данных эта оптимизация никогда не будет работать, но тогда можно было бы использовать PyMem_RawMalloc
вместо malloc
.Возможно, цель состояла в том, чтобы иметь возможность обернуть массивы вокруг памяти, выделенной C-подпрограммами, и взять на себя владение памятью (но в некоторых случаях это не сработает, см. Мой комментарий в конце этого поста).
Это объясняет поведение, которое вы наблюдаете: Для данных (размер которых изменяется в зависимости от переданного аргумента size в) используется PyDataMem_NEW
, который обходит распределитель памяти CPython, и вы видите оригинальное поведение распределителей C-runtime.
Следует избегать смешивания различных процедур выделения / освобождения PyArray_malloc
/ PyDataMem_NEW'/
malloc and
PyArray_free /
PyDataMem_FREE /
free`: даже если это работает на OS + Pythonимеющаяся версия может не работать для других комбинаций.
Например, в Windows, когда расширение построено с другой версией компилятора, один исполняемый файл может иметь разные распределители памяти из разных C-run-time и malloc/free
может связываться с различными C-памятью-распределителями, что может привести к затруднению отслеживания Доуn ошибок.