Управление памятью

Обзор

Управление памятью в Python вмещает в себя частную кучу, содержащую все объекты и структуры данных Python. Управление данной частной кучей обеспечивается внутренним устройством менеджера памяти Python. У менеджера памяти Python есть различные компоненты, которые работают с различными аспектами управления динамическим хранилищем, такими как совместное использование, сегментация, предварительное выделение или кэширование.

На самом нижнем уровне распределитель необработанной памяти гарантирует, что в частной куче достаточно места для хранения всех данных, связанных с Python, путём взаимодействия с менеджером памяти операционной системы. Помимо необработанного распределителя памяти, несколько объектно-ориентированных распределителей работают в одной и той же куче и реализуют различные политики управления памятью, адаптированные к особенностям каждого типа объекта. Например, целочисленные объекты управляются в куче иначе, чем строки, кортежи или словари, потому что целые числа подразумевают разные требования к хранилищу и компромиссы между скоростью и пространством. Таким образом, менеджер памяти Python делегирует часть работы объектно-ориентированным распределителям, но гарантирует, что последние работают в пределах частной кучи.

Важно понимать, что управление кучей Python выполняется самим интерпретатором и что пользователь не может управлять им, даже если они регулярно манипулируют указателями объектов на блоки памяти внутри этой кучи. Выделение пространства кучи для объектов Python и других внутренних буферов выполняется по запросу менеджером памяти Python с помощью функций API Python/C, перечисленных в данном документе.

Чтобы избежать повреждения памяти, разработчикам расширений никогда не следует пытаться работать с объектами Python с функциями, экспортируемыми библиотекой C: malloc(), calloc(), realloc() и free(). Это приведёт к смешанным вызовам между распределителем C и менеджером памяти Python с фатальными последствиями, поскольку они реализуют разные алгоритмы и работают с разными кучами. Однако можно безопасно выделять и освобождать блоки памяти с помощью распределителя библиотеки C для индивидуальных целей, как показано в следующем примере:

PyObject *res;
char *buf = (char *) malloc(BUFSIZ); /* для I/O */

if (buf == NULL)
    return PyErr_NoMemory();
...Do some I/O operation involving buf...
res = PyBytes_FromString(buf);
free(buf); /* malloc'ed */
return res;

В этом примере запрос памяти для буфера ввода-вывода обрабатывается распределителем библиотеки C. Менеджер памяти Python участвует только в распределении объекта байтов, возвращаемого в результате.

Однако в большинстве случаев рекомендуется выделять память из кучи Python специально потому, что последняя находится под управлением менеджера памяти Python. Например, это требуется, когда интерпретатор расширяется новыми типами объектов, написанными на C. Другой причиной использования кучи Python является желание сообщить менеджеру памяти Python о потребностях модуля расширения в памяти. Даже когда запрошенная память используется исключительно для внутренних, узкоспециализированных целей, делегирование всех запросов памяти менеджеру памяти Python приводит к тому, что интерпретатор получает более точное изображение своего объема памяти в целом. Следовательно, при определенных обстоятельствах менеджер памяти Python может или не может запускать соответствующие действия, такие как сборка мусора, уплотнение памяти или другие превентивные процедуры. Обратите внимание, что при использовании распределителя библиотеки C, как показано в предыдущем примере, выделенная память для буфера ввода-вывода полностью ускользает от менеджера памяти Python.

См.также

Переменная среды PYTHONMALLOC может использоваться для настройки распределителей памяти, используемого Python.

Переменная среды PYTHONMALLOCSTATS может использоваться для печати статистики pymalloc распределителя памяти каждый раз при создании новой арены объекта pymalloc и при завершении работы.

Интерфейс необработанной памяти

Следующие ниже наборы функций являются оболочками для системного распределителя памяти. Эти функции являются потокобезопасными, GIL не требует удержания.

Распределитель необработанной памяти по умолчанию использует следующие функции: malloc(), calloc(), realloc() и free(); вызвать malloc(1) (или calloc(1, 1)) при запросе нулевых байтов.

Добавлено в версии 3.4.

void* PyMem_RawMalloc(size_t n)

Выделяет n байта и возвращает указатель типа void* на выделенную память или NULL в случае сбоя запроса.

Запрос нулевых байтов возвращает по возможности отдельный указатель, отличный от NULL, как если бы вместо него был вызван PyMem_RawMalloc(1). Память никак не будет инициализирована.

void* PyMem_RawCalloc(size_t nelem, size_t elsize)

Выделяет nelem элементов, размер каждого из которых в байтах равен elsize, и возвращает указатель типа void* на выделенную память или NULL в случае сбоя запроса. Память инициализируется нулями.

Запрос нулевых элементов или элементов с размером нулевых байтов возвращает по возможности отдельный указатель, отличный от NULL, как если бы вместо этого был вызван PyMem_RawCalloc(1, 1).

Добавлено в версии 3.5.

void* PyMem_RawRealloc(void *p, size_t n)

Изменяет размер блока памяти, на который указывает p, до n байт. Содержимое будет неизменным до минимума старого и нового размеров.

Если pNULL, вызов эквивалентен PyMem_RawMalloc(n); иначе, если n равен нулю, размер блока памяти изменяется, но не освобождается, и возвращаемый указатель не является NULL.

Если p не является NULL, он должен быть возвращён предыдущим вызовом PyMem_RawMalloc(), PyMem_RawRealloc() или PyMem_RawCalloc().

Если запрос не выполняется, PyMem_RawRealloc() возвращает NULL, а p остается действительным указателем на предыдущую область памяти.

void PyMem_RawFree(void *p)

Освобождает блок памяти, на который указывает p, который должен был быть возвращён предыдущим вызовом PyMem_RawMalloc(), PyMem_RawRealloc() или PyMem_RawCalloc(). В противном случае или если ранее был вызван PyMem_RawFree(p), возникает неопределённое поведение.

Если pNULL, операция не выполняется.

Интерфейс памяти

Следующие наборы функций, смоделированные по стандарту ANSI C, но определяющие поведение при запросе нулевых байтов, доступны для выделения и освобождения памяти из кучи Python.

Распределитель памяти по умолчанию использует pymalloc распределитель памяти.

Предупреждение

При использовании этих функций необходимо удерживать GIL.

Изменено в версии 3.6: Распределителем по умолчанию теперь является pymalloc вместо системного malloc().

void* PyMem_Malloc(size_t n)

Выделяет n байтов и возвращает указатель типа void* на выделенную память или NULL в случае сбоя запроса.

Запрос нулевых байтов возвращает по возможности отдельный указатель, отличный от NULL, как если бы вместо него был вызван PyMem_Malloc(1). Память никак не будет инициализирована.

void* PyMem_Calloc(size_t nelem, size_t elsize)

Выделяет элементы nelem, размер каждого из которых в байтах равен elsize, и возвращает указатель типа void* на выделенную память или NULL в случае сбоя запроса. Память инициализируется нулями.

Запрос нулевых элементов или элементов размера нулевых байтов возвращает по возможности отдельный указатель, отличный от NULL, как если бы вместо него был вызван PyMem_Calloc(1, 1).

Добавлено в версии 3.5.

void* PyMem_Realloc(void *p, size_t n)

Изменяет размер блока памяти, на который указывает p, до n байт. Содержимое будет неизменным до минимума старого и нового размеров.

Если pNULL, вызов эквивалентен PyMem_Malloc(n); иначе, если n равно нулю, размер блока памяти изменяется, но не освобождается, и возвращаемый указатель не является NULL.

Если p не является NULL, он должен быть возвращён предыдущим вызовом PyMem_Malloc(), PyMem_Realloc() или PyMem_Calloc().

Если запрос не выполняется, PyMem_Realloc() возвращает NULL, а p остается действительным указателем на предыдущую область памяти.

void PyMem_Free(void *p)

Освобождает блок памяти, на который указывает p, который должен был быть возвращён предыдущим вызовом PyMem_Malloc(), PyMem_Realloc() или PyMem_Calloc(). В противном случае или если ранее был вызван PyMem_Free(p), возникает неопределенное поведение.

Если pNULL, операция не выполняется.

Для удобства предоставляются следующие макросы, ориентированные на типы. Обратите внимание, что TYPE относится к любому типу C.

TYPE* PyMem_New(TYPE, size_t n)

То же, что PyMem_Malloc(), но выделяет (n * sizeof(TYPE)) байт памяти. Возвращает указатель, приведенный к TYPE*. Память никак не будет инициализирована.

TYPE* PyMem_Resize(void *p, TYPE, size_t n)

То же, что PyMem_Realloc(), но размер блока памяти изменен до (n * sizeof(TYPE)) байт. Возвращает указатель, приведенный к TYPE*. При возврате p будет указателем на новую область памяти или NULL в случае сбоя.

Это макрос препроцессора C; p всегда переназначается. Сохраните исходное значение p, чтобы избежать потери памяти при обработке ошибок.

void PyMem_Del(void *p)

То же, что и PyMem_Free().

Кроме того, следующие наборы макросов предназначены для прямого вызова распределителя памяти Python без использования функций C API, перечисленных выше. Однако обратите внимание, что их использование не сохраняет двоичную совместимость между версиями Python и поэтому не рекомендуется в модулях расширения.

  • PyMem_MALLOC(size)
  • PyMem_NEW(type, size)
  • PyMem_REALLOC(ptr, size)
  • PyMem_RESIZE(ptr, type, size)
  • PyMem_FREE(ptr)
  • PyMem_DEL(ptr)

Распределители объектов

Следующие наборы функций, смоделированные по стандарту ANSI C, но определяющие поведение при запросе нулевых байтов, доступны для выделения и освобождения памяти из кучи Python.

Распределитель памяти по умолчанию использует pymalloc распределитель памяти.

Предупреждение

При использовании данных функций необходимо удерживать GIL.

void* PyObject_Malloc(size_t n)

Выделяет n байта и возвращает указатель типа void* на выделенную память или NULL в случае сбоя запроса.

Запрос нулевых байтов возвращает по возможности отдельный указатель, отличный от NULL, как если бы вместо него был вызван PyObject_Malloc(1). Память никак не будет инициализирована.

void* PyObject_Calloc(size_t nelem, size_t elsize)

Выделяет nelem элемента, размер каждого из которых в байтах равен elsize, и возвращает указатель типа void* на выделенную память или NULL в случае сбоя запроса. Память инициализируется нулями.

Запрос нулевых элементов или элементов с размером нулевых байтов возвращает по возможности отдельный указатель, отличный от NULL, как если бы вместо этого был вызван PyObject_Calloc(1, 1).

Добавлено в версии 3.5.

void* PyObject_Realloc(void *p, size_t n)

Изменяет размер блока памяти, на который указывает p, до n байт. Содержимое будет неизменным до минимума старого и нового размеров.

Если pNULL, вызов эквивалентен PyObject_Malloc(n); иначе, если n равно нулю, размер блока памяти изменяется, но не освобождается, и возвращаемый указатель не является NULL.

Если p не является NULL, он должен быть возвращён предыдущим вызовом PyObject_Malloc(), PyObject_Realloc() или PyObject_Calloc().

Если запрос не выполняется, PyObject_Realloc() возвращает NULL, а p остается действительным указателем на предыдущую область памяти.

void PyObject_Free(void *p)

Освобождает блок памяти, на который указывает p, который должен был быть возвращён предыдущим вызовом PyObject_Malloc(), PyObject_Realloc() или PyObject_Calloc(). В противном случае или если раньше был вызван PyObject_Free(p), возникает неопределенное поведение.

Если pNULL, операция не выполняется.

Распределители памяти по умолчанию

Распределители памяти по умолчанию:

Конфигурация Имя PyMem_RawMalloc PyMem_Malloc PyObject_Malloc
Релизная сборка "pymalloc" malloc pymalloc pymalloc
Отладочная сборка "pymalloc_debug" malloc + debug pymalloc + debug pymalloc + debug
Релизная сборка, без pymalloc "malloc" malloc malloc malloc
Отладочная сборка, без pymalloc "malloc_debug" malloc + debug malloc + debug malloc + debug

Легенда:

Настройка распределителя памяти

Добавлено в версии 3.4.

PyMemAllocatorEx

Структура, используемая для описания распределителя блоков памяти. В структуре четыре поля:

Поле Смысл
void *ctx контекст пользователя передан как первый аргумент
void* malloc(void *ctx, size_t size) выделить блок памяти
void* calloc(void *ctx, size_t nelem, size_t elsize) выделить блок памяти, инициализированный нулями
void* realloc(void *ctx, void *ptr, size_t new_size) выделить или изменить размер блока памяти
void free(void *ctx, void *ptr) освободить блок памяти

Изменено в версии 3.5: Структура PyMemAllocator была переименована в PyMemAllocatorEx и добавлено новое поле calloc.

PyMemAllocatorDomain

Enum используется для идентификации домена распределителя. Домены:

PYMEM_DOMAIN_RAW

Функции:

PYMEM_DOMAIN_MEM

Функции:

PYMEM_DOMAIN_OBJ

Функции:

void PyMem_GetAllocator(PyMemAllocatorDomain domain, PyMemAllocatorEx *allocator)

Получить распределитель блоков памяти указанного домена.

void PyMem_SetAllocator(PyMemAllocatorDomain domain, PyMemAllocatorEx *allocator)

Устанавливает распределитель блоков памяти указанного домена.

При запросе нулевых байтов новый распределитель должен возвращать отдельный указатель, отличный от NULL.

Для домена PYMEM_DOMAIN_RAW распределитель должен быть потокобезопасным: GIL не удерживается при вызове распределителя.

Если новый распределитель не является перехватчиком (не вызывает предыдущий распределитель), должна быть вызвана функция PyMem_SetupDebugHooks(), чтобы переустановить отладочные хуки поверх нового распределителя.

void PyMem_SetupDebugHooks(void)

Установочные хуки для обнаружения ошибок в функциях распределителя памяти Python.

Вновь выделенная память заполняется байтом 0xCD (CLEANBYTE), освобожденная память заполняется байтом 0xDD (DEADBYTE). Блоки памяти окружены «запрещенными байтами» (FORBIDDENBYTE: байт 0xFD).

Проверки во время выполнения:

  • Обнаружение нарушений API, например: PyObject_Free(), вызываемый в буфере, выделенном PyMem_Malloc()
  • Обнаружение записи до начала буфера (опустошение буфера)
  • Обнаружение записи после конца буфера (переполнение буфера)
  • Убедитесь, что GIL удерживается при вызове функций распределения доменов PYMEM_DOMAIN_OBJ (например, PyObject_Malloc()) и PYMEM_DOMAIN_MEM (например, PyMem_Malloc())

В случае ошибки отладочные хуки используют модуль tracemalloc для получения трассировки того места, где был выделен блок памяти. Трассировка отображается только в том случае, если tracemalloc отслеживает выделение памяти Python и блок памяти был отслежен.

Данные хуки установлены по умолчанию, если Python скомпилирован в режиме отладки. Переменная среды PYTHONMALLOC может использоваться для установки отладочных хуков на Python, скомпилированном в режиме релиза.

Изменено в версии 3.6: Эта функция теперь также работает на Python, скомпилированном в режиме релиза. В случае ошибки хуки отладки теперь используют tracemalloc для получения трассировки того места, где был выделен блок памяти. Отладочные хуки теперь также проверяют, удерживается ли GIL при вызове функций доменов PYMEM_DOMAIN_OBJ и PYMEM_DOMAIN_MEM.

Изменено в версии 3.8: Байтовые шаблоны 0xCB (CLEANBYTE), 0xDB (DEADBYTE) и 0xFB (FORBIDDENBYTE) были заменены на 0xCD, 0xDD и 0xFD, чтобы использовать те же значения, что и отладочные malloc() и free() Windows CRT.

Распределитель pymalloc

У Python распределитель pymalloc оптимизированн для небольших объектов (меньше или равных 512 байтам) с коротким временем жизни. Он использует сопоставления памяти, называемые «аренами», с фиксированным размером 256 КиБ. Он возвращается к PyMem_RawMalloc() и PyMem_RawRealloc() для выделений размером более 512 байт.

pymalloc — это распределитель по умолчанию доменов PYMEM_DOMAIN_MEM (например, PyMem_Malloc()) и PYMEM_DOMAIN_OBJ (например: PyObject_Malloc()).

Распределитель арены использует следующие функции:

  • VirtualAlloc() и VirtualFree() в Windows ,
  • mmap() и munmap(), если есть
  • malloc() и free() в противном случае.

Настройка распределителя арены pymalloc

Добавлено в версии 3.4.

PyObjectArenaAllocator

Структура, используемая для описания распределителя арены. В структуре три поля:

Поле Смысл
void *ctx контекст пользователя передан как первый аргумент
void* alloc(void *ctx, size_t size) выделить арену size байт
void free(void *ctx, size_t size, void *ptr) освободить арену
void PyObject_GetArenaAllocator(PyObjectArenaAllocator *allocator)

Получить распределитель арены.

void PyObject_SetArenaAllocator(PyObjectArenaAllocator *allocator)

Устанавливает распределитель арены.

tracemalloc C API

Добавлено в версии 3.7.

int PyTraceMalloc_Track(unsigned int domain, uintptr_t ptr, size_t size)

Отслеживайте выделенный блок памяти в модуле tracemalloc.

Возвращает 0 в случае успеха, возвращает -1 в случае ошибки (не удалось выделить память для хранения трассировки). Возвращает -2, если tracemalloc отключён.

Если блок памяти уже отслеживается, обновить существующую трассировку.

int PyTraceMalloc_Untrack(unsigned int domain, uintptr_t ptr)

Отслеживайте выделенный блок памяти в модуле tracemalloc. Ничего не делать, если блок не отслеживался.

Возвращает -2, если tracemalloc отключен, в противном случае возвращает 0.

Примеры

Далее пример из раздела Обзор, переписанный так, что буфер ввода- вывода выделяется из кучи Python с использованием первого набора функций:

PyObject *res;
char *buf = (char *) PyMem_Malloc(BUFSIZ); /* для I/O */

if (buf == NULL)
    return PyErr_NoMemory();
/* ...Выполнить некоторые операции ввода-вывода с использованием buf... */
res = PyBytes_FromString(buf);
PyMem_Free(buf); /* выделенный с PyMem_Malloc */
return res;

Тот же код, использующий множество функций, ориентированных на типы:

PyObject *res;
char *buf = PyMem_New(char, BUFSIZ); /* для I/O */

if (buf == NULL)
    return PyErr_NoMemory();
/* ...Выполнить некоторые операции ввода-вывода с использованием buf... */
res = PyBytes_FromString(buf);
PyMem_Del(buf); /* выделенный с PyMem_New */
return res;

Обратите внимание, что в двух приведенных выше примерах буфером всегда управляют с помощью функций, принадлежащих к одному и тому же множеству. Действительно, требуется использовать одно и то же семейство API памяти для данного блока памяти, чтобы сводить к минимуму риск смешивания разных распределителей. Следующая кодовая последовательность содержит две ошибки, одна из которых помечена как фатальная, поскольку она смешивает два разных распределителя, работающих в разных кучах.

char *buf1 = PyMem_New(char, BUFSIZ);
char *buf2 = (char *) malloc(BUFSIZ);
char *buf3 = (char *) PyMem_Malloc(BUFSIZ);
...
PyMem_Del(buf3);  /* Неправильно -- должен быть PyMem_Free() */
free(buf2);       /* Правильно -- выделено через malloc() */
free(buf1);       /* Фатальный -- должен быть PyMem_Del()  */

В дополнение к функциям, предназначенным для обработки необработанных блоков памяти из кучи Python, объекты в Python выделяются и релизаются с помощью PyObject_New(), PyObject_NewVar() и PyObject_Del().

Они будут объяснены в следующей главе, посвященной определению и реализации новых типов объектов в C.