Интерфейс для управления памятью api

Обновлено: 08.07.2024

Низкоуровневые языки программирования (например, C) имеют низкоуровневые примитивы для управления памятью, такие как malloc() и free() . В JavaScript же память выделяется динамически при создании сущностей (объектов, строк и т. п.) и "автоматически" освобождается, когда они больше не используются. Последний процесс называется сборкой мусора. Слово "автоматически" является источником путаницы и зачастую создаёт у программистов на JavaScript (и других высокоуровневых языках) ложное ощущение, что они могут не заботиться об управлении памятью.

Жизненный цикл памяти¶

Независимо от языка программирования, жизненный цикл памяти практически всегда один и тот же:

  1. Выделение необходимой памяти.
  2. Её использование (чтение, запись).
  3. Освобождение выделенной памяти, когда в ней более нет необходимости.

Первые два пункта осуществляются явным образом программистом во всех языках программирования. Третий пункт осуществляется явным образом в низкоуровневых языках, но в большинстве высокоуровневых языков, в том числе и в JavaScript, осуществляется автоматически.

Выделение памяти в JavaScript¶

Чтобы не утруждать программиста заботой о низкоуровневых операциях выделения памяти, интерпретатор JavaScript динамически выделяет необходимую память при объявлении переменных:

Вызовы некоторых функций также ведут к выделению памяти под объект:

Некоторые методы выделяют память для новых значений или объектов:

Использование значений¶

Использование значений, как правило, означает - чтение и запись значений из/в выделенной для них области памяти. Это происходит при чтении или записи значения какой-либо переменной, или свойства объекта или даже при передаче аргумента функции.

Освобождение памяти, когда она более не нужна¶

Именно на этом этапе появляется большинство проблем из области "управления памятью". Наиболее сложной задачей в данном случае является чёткое определение того момента, когда "выделенная память более не нужна". Зачастую программист сам должен определить, что в данном месте программы данная часть памяти более уже не нужна и освободить её.

Интерпретаторы языков высокого уровня снабжаются встроенным программным обеспечением под названием "сборщик мусора", задачей которого является следить за выделением и использованием памяти и при необходимости автоматически освобождать более не нужные участки памяти. Это происходит весьма приблизительно, так как основная проблема точного определения того момента, когда какая-либо часть памяти более не нужна - неразрешима (данная проблема не поддаётся однозначному алгоритмическому решению).

Сборка мусора¶

Достижимость¶

Основной концепцией управления памятью в JavaScript является принцип достижимости.

Если упростить, то "достижимые" значения - это те, которые доступны или используются. Они гарантированно находятся в памяти.

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

  • Локальные переменные и параметры текущей функции.
  • Переменные и параметры других функций в текущей цепочке вложенных вызовов.
  • Глобальные переменные.
  • (некоторые другие внутренние значения)

Эти значения мы будем называть корнями.

Любое другое значение считается достижимым, если оно доступно из корня по ссылке или по цепочке ссылок.

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

В интерпретаторе JavaScript есть фоновый процесс, который называется сборщик мусора. Он следит за всеми объектами и удаляет те, которые стали недостижимы.

Вот самый простой пример:

Здесь стрелка обозначает ссылку на объект. Глобальная переменная user ссылается на объект (мы будем называть его просто "John"). В свойстве "name" объекта John хранится примитив, поэтому оно нарисовано внутри объекта.

Если перезаписать значение user , то ссылка потеряется:

Теперь объект John становится недостижимым. К нему нет доступа, на него нет ссылок. Сборщик мусора удалит эти данные и освободит память.

Две ссылки¶

Представим, что мы скопировали ссылку из user в admin :

Теперь, если мы сделаем то же самое:

. то объект John всё ещё достижим через глобальную переменную admin , поэтому он находится в памяти. Если бы мы также перезаписали admin , то John был бы удалён.

Взаимосвязанные объекты¶

Теперь более сложный пример. Семья:

Функция marry "женит" два объекта, давая им ссылки друг на друга, и возвращает новый объект, содержащий ссылки на два предыдущих.

В результате получаем такую структуру памяти:

На данный момент все объекты достижимы.

Теперь удалим две ссылки:

Недостаточно удалить только одну из этих ссылок, потому что все объекты останутся достижимыми.

Но если мы удалим обе, то увидим, что у объекта John больше нет входящих ссылок:

Исходящие ссылки не имеют значения. Только входящие ссылки могут сделать объект достижимым. Объект John теперь недостижим и будет удалён из памяти со всеми своими данными, которые также стали недоступны.

После сборки мусора:

Недостижимый "остров"¶

Вполне возможна ситуация, при которой целый "остров" связанных объектов может стать недостижимым и удалиться из памяти.

Возьмём объект family из примера выше. А затем:

Структура в памяти теперь станет такой:

Этот пример демонстрирует, насколько важна концепция достижимости.

Объекты John и Ann всё ещё связаны, оба имеют входящие ссылки, но этого недостаточно.

У объекта family больше нет ссылки от корня, поэтому весь "остров" становится недостижимым и будет удалён.

Внутренние алгоритмы¶

Основной алгоритм сборки мусора - "алгоритм пометок" (англ. "mark-and-sweep").

Согласно этому алгоритму, сборщик мусора регулярно выполняет следующие шаги:

  • Сборщик мусора "помечает" (запоминает) все корневые объекты.
  • Затем он идёт по их ссылкам и помечает все найденные объекты.
  • Затем он идёт по ссылкам помеченных объектов и помечает объекты, на которые есть ссылка от них. Все объекты запоминаются, чтобы в будущем не посещать один и тот же объект дважды.
  • . И так далее, пока не будут посещены все ссылки (достижимые от корней).
  • Все непомеченные объекты удаляются.

Например, пусть наша структура объектов выглядит так:

Явно виден "недостижимый остров" справа. Теперь посмотрим, как будет работать "алгоритм пометок" сборщика мусора.

На первом шаге помечаются корни:

Затем помечаются объекты по их ссылкам:

. а затем объекты по их ссылкам и так далее, пока это вообще возможно:

Теперь объекты, до которых не удалось дойти от корней, считаются недостижимыми и будут удалены:

Это и есть принцип работы сборки мусора.

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

Вот некоторые из оптимизаций:

  • Сборка по поколениям (Generational collection) - объекты делятся на "новые" и "старые". Многие объекты появляются, выполняют свою задачу и быстро умирают, их можно удалять более агрессивно. Те, которые живут достаточно долго, становятся "старыми" и проверяются реже.
  • Инкрементальная сборка (Incremental collection) - если объектов много, то обход всех ссылок и пометка достижимых объектов может занять значительное время и привести к видимым задержкам выполнения скрипта. Поэтому интерпретатор пытается организовать сборку мусора поэтапно. Этапы выполняются по отдельности один за другим. Это требует дополнительного учёта для отслеживания изменений между этапами, но зато теперь у нас есть много крошечных задержек вместо одной большой.
  • Сборка в свободное время (Idle-time collection) - чтобы уменьшить возможное влияние на производительность, сборщик мусора старается работать только во время простоя процессора.

Существуют и другие способы оптимизации и разновидности алгоритмов сборки мусора. Но как бы мне ни хотелось описать их здесь, я должен воздержаться от этого, потому что разные интерпретаторы JavaScript применяют разные приёмы и хитрости. И, что более важно, всё меняется по мере развития интерпретаторов, поэтому углубляться в эту тему заранее, без реальной необходимости, вероятно, не стоит. Если, конечно, это не вопрос чистого интереса, тогда для вас будут полезны некоторые ссылки ниже.

Итого¶

Главное из того, что мы узнали:

  • Сборка мусора выполняется автоматически. Мы не можем ускорить или предотвратить её.
  • Объекты сохраняются в памяти, пока они достижимы.
  • Наличие ссылки не гарантирует, что объект достижим (от корня): несколько взаимосвязанных объектов могут стать недостижимыми как единое целое.

Современные интерпретаторы реализуют передовые алгоритмы сборки мусора.

Некоторые из них освещены в книге "The Garbage Collection Handbook: The Art of Automatic Memory Management" (R. Jones и др.).

Если вы знакомы с низкоуровневым программированием, то более подробная информация о сборщике мусора интерпретатора V8 находится в статье A tour of V8: Garbage Collection.

Для управления памятью, прикладным программам предоставляются различные интерфейсы: (первые 3 применимы в наст.время):

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

· MemoryMappedFileAPI – наборф-ий, позволяющих работать с файлами, отображаемыми в память; новый механизм, предоставляемый Win32API для работы с файлами и взаимодействия процессов;

· HeapMemoryAPI – набор ф-ий, позволяющих работать с динамически распределяемыми областями памяти (кучами).

· Local, GlobalMemoryAPI – набор ф-ий для работы с памятью, совместимых с х16 Windows (уже не используются).

· CRTMemoryAPI – ф-ии стандартной библиотеки языка С периода исполнения (runtime).

Интерфейс Virtual Memory.

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

Блок адресов адресного пространства процесса может нах-ся в одном из состояний:

-выделен (commited) – блоку адресов назначена физическая память, либо часть файла подкачки

-зарезервирован (reserved) – блок адресов помечен как занятый, но физическая память не распределена

-свободен (free) – блок адресов не выделен и не зарезервирован

При выделении память обнуляется.

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

VirtualAlloc () – выделяет память. Его параметры определяют: размер выделяемой памяти (не более 1 Гб), где в адресном пр-ве расположить выделенный фрагмент, надо ли закреплять физическую память, вид устанавливаемой защиты. Резервирует память фрагментами 64 кб, а закрепляет ее фрагментами объемом 1 страницу (4 кб).

VirtualProtect() –позволяет изменять атрибуты защиты в адресном пространстве текущего процесса.

VirtualProtectEx() –позволяет изменять атрибуты защиты в адресном пространстве произвольного процесса.

VirualQuery() – заполняет поля структуры информацией о заданном блоке памяти.

GlobalMemoryStatus() – определяет размер и свободный объем физической памяти, страничного файла и текущего адресного пространства.

GetSystemInfo() – возвращает размер системной физической страницы.

По завершении процесса ОС отменяет блокировку, освобождает использованную им память.

VirtualFree() – отменяет закрепление набора страниц, оставляя их адреса зарезервированными или освобождает память занимаемую этими страницами(даже заблокированные страницы).

Интерфейс Memory mapped file.

MemoryMappedFileAPI – набор ф-ий позволяющих работать с файлами, отображаемыми в память; новый механизм, предоставляемый Win32APIдля работы с файлами и взаимодействия процессов.

1. Исп-ся для запуска .exeи .dll.

-создания адресного простр-ва (размером 4 Гб)

-из ехефайластраницызагружаются в регион

-отображение в регион необходимыхdll.

2. Для работы с файлами.

-создается объект ядра «файл». CreateFile()

-создается объект ядра «проецируемый файл» CreateFileMapping(). Возвращает Handle.

-проецирование файла в вирт.память MapViewOfFile().

-убрать проецирование в память: UnmapViewFile().

Уничтожение объектов «файл», «проецируемый файл» - CloseHandle().

3. Для одновременного использования одной области данных несколькими процессами.

2 процесса могут использовать совместно, объект «проецируемый файл». При помощи MapViewOfFile() каждый процесс проецирует этот объект на свое адресное пространство и использует эту часть адресного пространства как разделяемую область данных.


Интерфейс Heap Memory.

HeapMemoryAPI – набор ф-ий, позволяющих работать с динамически распределяемыми областями памяти (кучами).

Куча – блок памяти, из которого прога при необходимости выделяет себе более мелкие фрагменты.

Причины группировки выделенных блоков:

-позволяет отделить и защитить группу связанных блоков.

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

-объекты памяти, работающие совместно могут быть сгруппированы => подкачка страниц сводится к минимуму.

GetProcessHeap() – получить дескриптор стандартной кучи (по умолчанию) памяти.

HeapCreate() – создает кучу. В параметрах указывается начальный размер кучи и максимальный размер кучи (если он =0, то размер кучи ограничивается объемом доступной памяти).

HeapAlloc() – выделение блоков памяти из кучи.

HeapReAlloc() – повторное выделение…(изменение размера блока, после его выделения).

HeapFree() – освобождение блоков памяти.

HeapSize() –узнать точный размер любого блока.

HeapDestroy() – уничтожение «кучи».


Организация стока поверхностных вод: Наибольшее количество влаги на земном шаре испаряется с поверхности морей и океанов (88‰).



Общие условия выбора системы дренажа: Система дренажа выбирается в зависимости от характера защищаемого.


Опора деревянной одностоечной и способы укрепление угловых опор: Опоры ВЛ - конструкции, предназначен­ные для поддерживания проводов на необходимой высоте над землей, водой.

3. Свободен (free) - блок адресов не выделен и не зарезервирован.

Резервирование и выделение памяти производится блоками. Начальный адрес бло-

ка должен быть выровнен на границу 64K (округляется вниз), а размер кратен размеру

страницы (округляется вверх). При выделении память обнуляется.

Для резервирования региона памяти в адресном пространстве процесса или выде-

ления ее используется функция VirtualAlloc, а для освобождения - функция VirtualFree.

Функция VirtualAlloc является базовой при выполнении операций управления вир-

туальным адресным пространством. Параметры этой функции определяют, какой объем

памяти необходимо выделить, в каком месте адресного пространства должен распола-

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

щиты следует установить. Функция возвращает адрес выделенного региона или NULL в

LPVOID VirtualAlloc ( LPVOID lpvAddress,

// адрес для размещения нового блока

// размер нового блока

DWORD fdwAllocationType,// зарезервировать адреса или закрепить

// нет доступа, только чтение

Функция VirtualAlloc сначала пробует найти область свободных адресов размером

dwSize байтов, которая начинается с адреса lpvAddress. Для этого она просматривает де-

рево VAD. Если необходимая область памяти свободна, функция возвращает значение

lpvAddress. В противном случае она просматривает все адресное пространство и ищет

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

возвращает его начальный адрес, иначе - значение NULL.






































Аргумент fdwAllocationType может принимать значение MEM_RESERVE или

MEM_COMMIT либо оба значения одновременно. Для резервирования определенного

интервала адресов функция VirtualAlloc создает новый VAD, который отмечает исполь-

зуемую область. Однако эта функция не выделяет физическую память, из-за чего невоз-

можно использовать зарезервированные адреса. При попытке чтения или записи в заре-

зервированные страницы возникает ошибка доступа к памяти. С другой стороны,

никакая другая команда выделения памяти не может использовать ранее зарезервиро-

ванные адреса. Например, функции GlobalAlloc и malloc не могут разместить новые объ-

екты в области, которая пересекается с зарезервированным адресным пространством.

Попытка заставить функцию VirtualAlloc зарезервировать все доступное адресное про-

странство (1 Гб) приведет к конфликту: последующие операции выделения памяти не

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

Память не может быть закреплена, если она не зарезервирована. Комбинация фла-

гов MEM_RESERVE и MEM_COMMIT позволяет одновременно зарезервировать и за-

крепить указанную область памяти. Часто программисты вызывают функцию VirtualAl-

loc сначала с флагом MEM_RESERVE для резервирования большой области памяти, а

затем несколько раз подряд с флагом MEM_COMMIT для поэтапного закрепления от-

Флаг fdwProtect определяет, каким образом использовать определенную страницу

или диапазон страниц. Для резервируемой памяти этот флаг должен иметь значение

PAGE_NOACCESS. При закреплении памяти устанавливается флаг PAGE_READONLY

или PAGE_READWRITE. Другие программы не могут читать информацию из адресного

пространства вашего процесса, поэтому режим доступа только для чтения обеспечивает

защиту от ошибок в вашей программе, которые могут привести к случайному поврежде-

нию важной информации. Уровни защиты применимы к отдельным страницам. Различ-

ные страницы в одной области памяти могут иметь разные значения флага защиты. На-

пример, вы можете применить флаг PAGE_READONLY ко всему блоку, а затем

временно изменять уровень защиты отдельных страниц, разрешая доступ к ним для за-

писи. Защитить от записи только часть страницы невозможно, поскольку флаги устанав-

ливаются для целых страниц. В таблице 4.1. приведены возможные значения, которые

может принимать флаг fdwProtect.

Допускается только чтение

Допускается чтение и запись

Допускается только исполнение

Допускается исполнение и чтение

PAGE_EXECUTE_READWRITE Допускается исполнение чтение и запись

Дополнительный флаг защиты, который комбини-

руется с другими флагами. При первом обращении

к странице этот флаг сбрасывается и возникает ис-

ключение STATUS_GUARD_PAGE. Этот флаг ис-

пользуется для контроля размеров стека с возмож-

ностью его динамического расширения.

Запрещает кэширование страниц. Может быть поле-

зен при разработке драйверов устройств (например,

данные в видеобуфер должны переписываться сра-

зу, без кэширования)

Функция VirtualAlloc не может зарезервировать более 1 Гб памяти, поскольку про-

цесс контролирует только нижнюю половину своего адресного пространства объемом 2

Гб. В действительности объем контролируемой памяти еще меньше из-за свободных об-

ластей (по 64 Кб каждая) на границах адресного пространства процесса (рис. 8.2). Кроме

того, функция VirtualAlloc резервирует память фрагментами по 64 Кб, а закрепляет ее

фрагментами объемом в одну страницу. При резервировании памяти функция VirtualAl-

loc округляет аргумент lpvAddress до ближайшего значения, кратного 64 Кб. При закре-

плении памяти функция VirtualAlloc осуществляет одно из двух действий. Если аргу-

мент lpvAddress имеет значение NULL, функция VirtualAlloc округляет значение

аргумента dwsize до ближайшей границы между страницами. Если значение аргумента

lpvAddress не равно NULL, она закрепляет все страницы, содержащие хотя бы один байт

информации в диапазоне адресов от lpvAddress до lpvAddress + dwSize. Например, если

при выделении двух байтов памяти заданный адрес пересекает границу двух страниц,

закрепляются две целые страницы. В большинстве систем Windows 98 размер страницы

составляет 4 Кб, но если вы хотите проверить это значение, вызовите функцию GetSys-

По завершении процесса система автоматически освобождает использовавшуюся

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

BOOL VirtualFree (LPVOID lpvAddress,

// адрес освобождаемого блока

// размер освобождаемого блока

// перевести в резерв или освободить

Функция VirtualFree отменяет закрепление набора страниц, оставляя их адреса за-

резервированными, или полностью освобождает память, занимаемую этими страницами.

Закрепление можно отменять маленькими блоками, содержащими как зарезервирован-

ные, так и закрепленные страницы.

При освобождении зарезервированных адресов необходимо очищать весь выде-

ленный блок, причем все страницы этого блока должны находиться в одинаковом со-

стоянии - либо в закрепленном, либо в зарезервированном. Аргумент lpvAddress должен

содержать базовый адрес, возвращенный функцией VirtualAlloc. Значение аргумента

dwSize игнорируется, поскольку сразу освобождается весь выделенный диапазон: Аргу-

мент dwSize учитывается лишь при отмене закрепления отдельных фрагментов. Аргу-

мент fdwFreeType может принимать следующие значения:

MEM_RELEASE - освободить зарезервированный регион. При использовании этого

флага параметр dwSize должен быть равен нулю.

MEM_DECOMMIT - Освободить выделенную память.

В программах, где используются команды для работы с виртуальной памятью,

должен быть предусмотрен механизм "сбора мусора", обеспечивающий освобождение

страниц, которые становятся пустыми. В качестве такого механизма может применяться

низкоприоритетный поток, который время от времени просматривает выделенную об-

ласть и ищет пустые страницы.

Для изменения атрибутов защиты регионов используются функции VirtualProtect и

VirtualProtectEx. Причем, первая позволяет изменять атрибуты защиты в адресном про-

странстве текущего процесса, а вторая -произвольного. Рассмотрим подробнее функцию

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

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

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

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

В этом примере запрос памяти для 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.

Выделяет 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.

Изменяет размер блока памяти, на который указывает 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 кучи.

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

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

Выделяет 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.

Изменяет размер блока памяти, на который указывает 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 ) ¶

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

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

Выделяет 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.

Изменяет размер блока памяти, на который указывает 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.

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

Поле Смысл
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 .

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

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

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

Новый аллокатор должен возвращает отдельный указатель не- 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 байт.

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

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

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

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

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

Поле Смысл
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 .

Примеры¶

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

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

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

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

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

Читайте также: