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

Обзор

Управление памятью в 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;

В этом примере запрос памяти для I/O буфера обрабатывается аллокатором библиотеки C. Диспетчер памяти Python участвует только в выделении байтов объекта, возвращенный в результате.

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

Если p равно NULL, вызов эквивалентен 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) вызывался ранее, возникает неопределенное поведение.

При p NULL операция не выполняется.

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

Следующие наборы функций, смоделированные после стандарта 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 байт. Содержимое будет неизменным до минимума старого и нового размеров.

Если p равно NULL, вызов эквивалентен 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) вызывался ранее, возникает неопределенное поведение.

При p NULL операция не выполняется.

Для удобства предоставляются следующие ориентированные на тип макросы. Обратите внимание, что 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 байт. Содержимое будет неизменным до минимума старого и нового размеров.

Если p равно NULL, вызов эквивалентен 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) вызывался ранее, возникает неопределенное поведение.

При p NULL операция не выполняется.

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

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

Конфигурация Имя 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

Легенда:

  • Имя: значение для переменной окружения PYTHONMALLOC
  • malloc: системные распределители из стандарта библиотеки C, C функции: malloc(), calloc(), realloc() и free()
  • pymalloc: pymalloc аллокатор памяти
  • «+ debug»: с отладочными хуками установленными PyMem_SetupDebugHooks()

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

Добавлено в версии 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

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

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 KiB. Он возвращается к PyMem_RawMalloc() и PyMem_RawRealloc() для аллокаций размером более 512 байт.

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

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

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

Настройка аллокатора pymalloc Arena

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

PyObjectArenaAllocator

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

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

Взять аллокатор арены.

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.

Примеры

Вот пример из раздела Обзор, перезаписанный таким образом, что буфер I/O аллоцированный из Python кучи с помощью первого набора функций:

PyObject *res;
char *buf = (char *) PyMem_Malloc(BUFSIZ); /* for I/O */

if (buf == NULL)
    return PyErr_NoMemory();
/* ...Do some I/O operation involving buf... */
res = PyBytes_FromString(buf);
PyMem_Free(buf); /* выделен с PyMem_Malloc */
return res;

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

PyObject *res;
char *buf = PyMem_New(char, BUFSIZ); /* for 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.