Garbage Collector для Delphi

Несколько лет назад решил попробовать создать Garbage Collector (далее GC) в Delphi, опираясь на богатую на возможности RTTI. Что нужно для работы GC? Один из подходов — обходить все объекты и определять, какие еще используются программой.

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

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

RTTI поможет анализировать объекты, т.к. хранит информацию о типе каждого поля объекта. Т.е. мы может определить на какие еще объекты ссылается каждый объект.

Хм … Но Delphi не хранит информацию о типе расположенной в стеке переменной.
Как и о типе глобальных переменных. Это просто адреса и данные …

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

Неожиданно «сложным» оказался процесс удаления найденных «зацикленных» объектов. Просто так вызвать Free нельзя, на него ссылаются, возникает ошибка «Invalid pointer operation» в методе TInterfacedObject.BeforeDestruction.

procedure TInterfacedObject.BeforeDestruction;
begin
  if RefCount <> 0 then
    Error(reInvalidPtr);
end;

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

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

И как же выглядит использование объектов с GC? Примерно так:

Obj1 := TClass1.Create;
Obj2 := TClass2.Create;
...
работа с объектами
...

Сравним с традиционной схемой работы:

Obj1 := TClass1.Create;
try
  Obj2 := TClass2.Create;
  try
    ...
    работа с объектами
    ...
  finally
    FreeAndNil( Obj2 ); // ну или просто Obj2.Free;
  end;
finally
  FreeAndNil( Obj1 ); // ну или просто Obj1.Free;
end;

Заметно меньше кода, нет лестницы из вложенных try — finally. Читать и разбираться с кодом заметно проще.

А что насчет быстродействия?

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

50 000 объектов на моем компьютере GC сканирует примерно за секунду. Не слишком высокая скорость. Понятно, что чем реже запускается GC, тем меньше ощущаются подтормаживания. Идеально, когда он вообще не запускается.

Итог

Что дало использование GC в Delphi:

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

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

На данный момент указанный модуль работает в Delphi XE2 — XE8. И не работает в Delphi 10. При востребованности модуля будем дорабатывать и под новые версии Delphi.

Демо-приложение и модуль GC доступны здесь:

Скачать GC for Delphi (скачиваний: 2510)

Для применения в серьезных проектах пока не годится. Это скорее работающий черновик. В нем много ограничений и есть, что дорабатывать. Зато доказана возможность реализации Garbage Collector в Delphi, используя встроенные механизмы.

P.S. Сейчас ведется работа над фоновым GC, для работы которого не нужно останавливать остальные потоки. Эта версия вообще не будет приводить к заметным зависаниям. Просто часть ресурсов (несколько процентов) постоянно будет использоваться сборщиком мусора.

Оставьте комментарий