Статический адрес объекта CPython и фрагментация - PullRequest
0 голосов
/ 05 марта 2019

Я читаю

Для CPython id (x) - это адрес памяти, где хранится x.

И это заданная id объектаникогда не изменяется, что означает, что объект всегда хранится по заданному адресу памяти в течение своего времени жизни.Это приводит к вопросу: как насчет фрагментации (виртуальной) памяти?

Скажем, объект A находится по адресу 1 (имеет id 1), занимает 10 байтов, поэтому он занимает адреса 1-10.Объект B имеет id 11 и занимает байты 11-12, а объект C занимает адреса 13-22.Как только B выйдет из области видимости и получит GC'd, у нас будет фрагментация.

Как разрешается эта загадка?

1 Ответ

0 голосов
/ 06 марта 2019

CPython использует собственный распределитель памяти для небольших объектов, pymalloc-allocator .Неплохое описание можно найти в самом коде .

Этот распределитель довольно хорош во избежании фрагментации памяти, поскольку он эффективно использует освобожденную память.Однако это всего лишь эвристика, и можно было бы придумать сценарии, которые приводят к фрагментации памяти.

Давайте разберемся, что происходит, когда мы выделяем объект размером 1 байт.

CPython имеет свойсобственная так называемая арена для объектов размером менее 512 байт.Очевидно, что 1-байтовый запрос будет управляться его распределителем.

Запрошенные размеры разделены на 64 различных класса: 0-й класс предназначен для размеров 1,8 байта, 1-й класс предназначен для размеров или 9 ..16 байтов и т. Д. - это связано с необходимостью выравнивания 8 байтов.Каждый из вышеперечисленных классов имеет свою более или менее независимую / выделенную память.Наш запрос для 0-го класса.

Давайте предположим, что это первый запрос для этого класса размера.Будет создан новый «пул» или будет использован пустой пул.Пул размером 4 КБ и, следовательно, имеет место для 512 8-байтовых «блоков».Несмотря на запрос только 1 байта, мы будем блокировать еще 7 байтов занятого блока, поэтому они не могут быть использованы для других объектов.Все свободные блоки хранятся в списке - в начале все 512 блоков находятся в этом списке.Распределитель удаляет первый блок из этого списка свободных блоков и возвращает его адрес в качестве указателя.

Сам пул помечен как «используемый» и добавлен в список используемых пулов для 0-го класса.

Теперь выделение другого объекта размером <= 8 байт происходит следующим образом.Сначала мы рассмотрим список используемых пулов для 0-го класса и найдем пул, который уже используется, то есть имеет несколько используемых и несколько свободных блоков.Allocator использует первый свободный блок, удаляет его из списка свободных блоков и возвращает его адрес в качестве указателя. </p>

Удалить первый объект легко - мы добавляем занятый блок в качестве заголовка списка свободных блоков в (пока один) используемый пул.

Когда создается новый объект размером 8 байт, поэтому используется первый блок в списке свободных блоков, и это блок, который использовался первым, теперьудален, объект.

Как видите, память используется повторно, и, таким образом, фрагментация памяти значительно уменьшается.Это не означает, что не может быть фрагментации памяти:

После выделения 512 однобайтовых объектов первый пул становится «полным», и будет создан / использован новый пул для размеров 0-го класса.Как только мы добавим еще 512 объектов, второй пул станет «полным».И так далее.

Теперь, если удаляются первые 511 элементов - будет еще один байт, блокирующий целые 4 КБ, которые нельзя использовать для других классов.

Только после освобождения последнего блока пул становится «пустым» и, следовательно, может использоваться повторно для других классов размеров.


Пустые пулы не возвращаются в ОС,но оставайтесь на арене и используйте их повторно.Однако pymalloc управляет несколькими аренами , и если арена становится «неиспользуемой», она может быть освобождена и занятая память (т. Е. Пулы) возвращается в ОС.

...