E.3 Сборка мусора

Когда программа создает список или пользователь определяет новую функцию (например, загружая библиотеку), эти данные помещаются в обычное хранилище. Если обычное хранилище заканчивается, Emacs запрашивает у операционной системы выделение дополнительной памяти. Различные типы объектов Lisp, такие как символы, ячейки cons, небольшие векторы, маркеры и т. д., разделены в отдельные блоки в памяти. (Большие векторы, длинные строки, буферы и некоторые другие типы редактирования, которые довольно велики, выделяются в отдельных блоках, по одному на объект; небольшие строки упаковываются в блоки по 8 КБ, а небольшие векторы упаковываются в блоки по 4 КБ).

Помимо базового вектора, многие объекты, такие как маркеры, оверлеи и буферы, управляются как векторы. Соответствующие структуры данных C включают поле union vectorlike_header, в котором член size содержит подтип, перечисляемый enum pvec_type, и информацию о том, сколько полей Lisp_Object содержит эта структура, и каков размер оставшихся данных. Эта информация необходима для вычисления объема памяти объекта и используется кодом выделения векторов при итерации по блокам векторов.

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

Когда пометка завершена, все необмеченные объекты являются мусором. Независимо от того, что делает программа Lisp или пользователь, невозможно обратиться к ним, поскольку больше нет способа достичь их. Их пространство можно использовать повторно, поскольку никто их не потеряет. Второй (этап очистки) этап сборщика мусора организует их повторное использование. (Но поскольку пометка была выполнена «консервативно», не все неиспользуемые объекты гарантированно будут собраны мусором при одной очистке.)

Этап очистки помещает неиспользуемые ячейки cons в свободный список для будущего выделения; то же самое относится к символам и маркерам. Он компактирует доступные строки, чтобы они занимали меньше блоков по 8 КБ; затем освобождает другие блоки по 8 КБ. Недостижимые векторы из блоков векторов объединяются для создания максимально возможных свободных областей; если свободная область охватывает полный блок по 4 КБ, этот блок освобождается. В противном случае свободная область регистрируется в массиве свободного списка, где каждая запись соответствует свободному списку областей одинакового размера. Большие векторы, буферы и другие большие объекты выделяются и освобождаются индивидуально.

Примечание для Common Lisp: В отличие от других Lisp, GNU Emacs Lisp не вызывает сборщик мусора, когда свободный список пуст. Вместо этого он просто запрашивает у операционной системы выделение дополнительного хранилища, и обработка продолжается до тех пор, пока не будет использовано gc-cons-threshold байт.

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

Команда: garbage-collect

Эта команда выполняет сборку мусора и возвращает информацию об объеме используемого пространства. (Сборка мусора также может произойти спонтанно, если вы используете более gc-cons-threshold байтов данных Lisp с момента последней сборки мусора.)

garbage-collect возвращает список с информацией об объеме используемого пространства, где каждая запись имеет вид «(имя размер использовано)» или «(имя размер использовано свободно)». В записи имя — это символ, описывающий тип объектов, которые представляет эта запись, размер — это количество байтов, используемых каждым из них, использовано — это количество таких объектов, которые были обнаружены живыми в куче, а необязательное свободно — это количество таких объектов, которые не живы, но Emacs сохраняет их для будущих выделений. Таким образом, общий результат выглядит следующим образом:

((conses cons-size used-conses free-conses)
 (symbols symbol-size used-symbols free-symbols)
 (strings string-size used-strings free-strings)
 (string-bytes byte-size used-bytes)
 (vectors vector-size used-vectors)
 (vector-slots slot-size used-slots free-slots)
 (floats float-size used-floats free-floats)
 (intervals interval-size used-intervals free-intervals)
 (buffers buffer-size used-buffers)
 (heap unit-size total-size free-size))

Вот пример:

(garbage-collect)
      ⇒ ((conses 16 49126 8058) (symbols 48 14607 0)
                 (strings 32 2942 2607)
                 (string-bytes 1 78607) (vectors 16 7247)
                 (vector-slots 8 341609 29474) (floats 8 71 102)
                 (intervals 56 27 26) (buffers 944 8)
                 (heap 1024 11715 2678))

Ниже представлена таблица, объясняющая каждый элемент. Обратите внимание, что последняя запись heap является необязательной и присутствует только в том случае, если базовый метод malloc предоставляет функцию mallinfo.

cons-size

Внутренний размер ячейки cons, т.е. sizeof (struct Lisp_Cons).

used-conses

Количество используемых ячеек cons.

free-conses

Количество ячеек cons, для которых было получено пространство от операционной системы, но которые в настоящее время не используются.

symbol-size

Внутренний размер символа, т.е. sizeof (struct Lisp_Symbol).

used-symbols

Количество используемых символов.

free-symbols

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

string-size

Внутренний размер заголовка строки, т.е. sizeof (struct Lisp_String).

used-strings

Количество используемых заголовков строк.

free-strings

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

byte-size

Это используется для удобства и равно sizeof (char).

used-bytes

Общий размер всех строковых данных в байтах.

vector-size

Размер в байтах вектора длиной 1, включая его заголовок.

used-vectors

Количество выделенных из блоков векторов заголовков векторов.

slot-size

Внутренний размер слота вектора, всегда равный sizeof (Lisp_Object).

used-slots

Количество слотов во всех используемых векторах. Подсчет слотов может включать часть или весь оверхед от заголовков векторов, в зависимости от платформы.

free-slots

Количество свободных слотов во всех блоках векторов.

float-size

Внутренний размер объекта float, т.е. sizeof (struct Lisp_Float). (Не путайте с нативным типом платформы float или double.)

used-floats

Количество используемых float.

free-floats

Количество float, для которых было получено пространство от операционной системы, но которые в настоящее время не используются.

interval-size

Внутренний размер объекта интервала, т.е. sizeof (struct interval).

used-intervals

Количество используемых интервалов.

free-intervals

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

buffer-size

Внутренний размер буфера, т.е. sizeof (struct buffer). (Не путайте с значением, возвращаемым функцией buffer-size.)

used-buffers

Количество используемых объектов буферов. Это включает закрытые буферы, невидимые пользователям, т.е. все буферы в списке all_buffers.

unit-size

Единица измерения пространства кучи, всегда равна 1024 байтам.

total-size

Общий размер кучи в единицах unit-size.

free-size

Неиспользуемое пространство кучи в единицах unit-size.

Если произошел переполнение в чистом хранилище (см. Чистое хранилище), и Emacs был выгружен с использованием (теперь устаревшего) метода unexec (см. Сборка Emacs), то garbage-collect возвращает nil, потому что в этом случае не может быть выполнена настоящая сборка мусора.

Пользовательский вариант: garbage-collection-messages

Если эта переменная не равна nil, Emacs выводит сообщение в начале и в конце сборки мусора. Значение по умолчанию — nil.

Переменная: post-gc-hook

Это обычный хук, который выполняется в конце сборки мусора. Сборка мусора приостанавливается во время выполнения функций хука, поэтому будьте осторожны при их написании.

Пользовательский вариант: gc-cons-threshold

Значение этой переменной — это количество байтов хранилища, которое должно быть выделено для объектов Lisp после одной сборки мусора, чтобы запустить другую сборку мусора. Вы можете использовать результат, возвращаемый garbage-collect, чтобы получить информацию о размере определенного типа объекта; пространство, выделенное для содержимого буферов, не учитывается.

Начальное значение порога — GC_DEFAULT_THRESHOLD, определенное в alloc.c. Поскольку оно определено в единицах word_size, значение составляет 400 000 для конфигурации по умолчанию 32-бит, и 800 000 для 64-битной и для 32-битных сборок, скомпилированных с опцией --with-wide-int. Если вы укажете большее значение, сборка мусора будет происходить реже. Это уменьшает время, затрачиваемое на сборку мусора (поэтому программы Lisp будут работать быстрее между циклами сборки мусора, которые происходят реже), но увеличивает общее использование памяти. Вы можете захотеть сделать это при запуске программы, которая создает много данных Lisp, особенно если вам нужно, чтобы она работала быстрее. Однако мы не рекомендуем увеличивать порог в продолжительные периоды времени и советуем никогда не устанавливать его выше, чем необходимо для работы программы в разумное время. Использование порогов, более высоких, чем необходимо, может потенциально вызвать более сильное нагрузку на память всей системы, а также может привести к тому, что каждый цикл сборки мусора займет гораздо больше времени, поэтому этого следует избегать.

Вы можете сделать сборки более частыми, указав меньшее значение, вплоть до 1/10 от GC_DEFAULT_THRESHOLD. Значение ниже этого минимума останется в силе только до последующей сборки мусора, после которой garbage-collect сбросит порог обратно до минимума.

Пользовательский вариант: gc-cons-percentage

Значение этой переменной указывает объем консирования перед прохождением сборки мусора, в долях от текущего размера кучи. Этот критерий и gc-cons-threshold применяются параллельно, и сборка мусора происходит только тогда, когда оба критерия удовлетворены.

По мере увеличения размера кучи время выполнения сборки мусора увеличивается. Таким образом, может быть желательно выполнять их реже пропорционально.

Начальное процентное значение составляет 0.1 в интерактивных сессиях и при выгрузке Emacs (см. Сборка Emacs), и 1.0 в неинтерактивных (также известных как «пакетных») сессиях.

Как и в случае с gc-cons-threshold, не увеличивайте это более чем необходимо и никогда не делайте этого в течение длительных периодов времени.

Контроль над сборщиком мусора с помощью gc-cons-threshold и gc-cons-percentage является только приблизительным. Хотя Emacs регулярно проверяет исчерпание порога, по соображениям эффективности он не делает этого сразу после каждого изменения кучи или gc-cons-threshold или gc-cons-percentage, поэтому исчерпание порога не немедленно запускает сборку мусора. Кроме того, для повышения эффективности расчетов порога Emacs аппроксимирует размер кучи, который подсчитывает байты, используемые в настоящее время доступными объектами в куче.

Значение, возвращаемое garbage-collect, описывает объем памяти, используемой данными Lisp, разбитый по типам данных. В отличие от этого, функция memory-limit предоставляет информацию об общем объеме памяти, который в настоящее время использует Emacs.

Функция: memory-limit

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

Переменная: memory-full

Эта переменная равна t, если Emacs почти исчерпал память для объектов Lisp, и nil в противном случае.

Функция: memory-use-counts

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

Функция: memory-info

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

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

Переменная: gcs-done

Эта переменная содержит общее количество выполненных сборок мусора в этой сессии Emacs.

Переменная: gc-elapsed

Эта переменная содержит общее количество секунд прошедшего времени во время сборки мусора в этой сессии Emacs, в виде числа с плавающей точкой.

Функция: memory-report

Иногда может быть полезно увидеть, где Emacs использует память (в различных переменных, буферах и кэшах). Эта команда откроет новый буфер (названный «"*Memory Report*"») , который предоставит обзор, в дополнение к списку «самых больших» буферов и переменных.

Все данные здесь приблизительны, потому что нет по-настоящему согласованного способа вычисления размера переменной. Например, две переменные могут иметь общие части структуры данных, и это будет учтено дважды, но эта команда все равно может дать полезный высокоуровневый обзор того, какие части Emacs используют память.