воскресенье, 1 ноября 2009 г.

GC or not GC

that is the question :)

Мне часто приходилось встречаться с таким мнением, мол в С++ нет сборки мусора, но она и не нужна, так-как есть умные указатели, с помощью которых можно так-же легко управлять памятью используя подсчет ссылок. Так вот, я думаю, что все это ерунда и попробую сейчас объяснить почему, в качестве примера умного указателя считающего ссылки, буду использовать boost::shared_ptr, под сборщиком мусора, я собираюсь понимать сборщик мусора CLR. :)

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

Операции над указателями: в случае GC, мы просто копируем их, в случае not GC, копирование указателя приводит к изменению счетчика ссылок объекта. Копирование, передача в качестве параметра в функцию, удаление умного указателя, приводят к генерации барьера и изменению счетчика ссылок. К тому-же, сам указатель и его счетчик ссылок, могут находится далеко друг от друга в памяти, а это означает плохую локальность.

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

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

Итак, подведу итог. Если использовать в качестве критерия сложность, то получается следующее: сам по себе, алгоритм подсчета ссылок очень прост, алгоритм работы современного сборщика мусора значительно сложнее. Но с точки зрения разработчика все наоборот. Сложности, связанные со сборкой мусора, берет на себя рантайм, а с подсчетом ссылок должен управляться сам программист. Он должен следить за тем, что-бы не возникали циклические ссылки, что-бы у одного объекта всегда был только один счетчик ссылок и так далее. В случае использования GC, нужно просто стараться не создавать для него лишнюю работу. Ну и самое главное – архитектура становится проще.

В плане производительности, делайте выводы сами, лично мне они кажутся достаточно очевидными. С днем всех святых! :)

14 комментариев:

  1. насколько я помню, сборщик мусора CLR отнюдь не идеален, и при использовании ФЯ (и выделении очень большого кол-ва маленьких объектов), с ним возникает много проблем и проседаний в производительности - это кто-то из разработчиков F# писал

    ОтветитьУдалить
  2. У меня недостаточно опыта, для того, что-бы судить, насколько сборщик мусора CLR хорош для ФЯ. Я недавно читал одну статью в MSDN. Там говорится о том, что нужно минимизировать количество изменений объектов, особенно тех, которые живут долго. Значит, неизменяемость объектов в ФЯ, хорошо скажется на производительности сборщика мусора CLR.

    ОтветитьУдалить
  3. ну судя по статье, CLR реализует стандартную схему с generational gc, но видимо были какие-то особенности реализации, которые просаживали производительность для маленьких объектов - я если найду сслыку на то обсуждение, то кину в комментарий

    ОтветитьУдалить
  4. Операции над указателями: в случае GC, мы просто копируем их, в случае not GC, копирование указателя приводит к изменению счетчика ссылок объекта.

    А разве для того, чтобы работал GC у каждого объекта не присутвует счетчик ссылок, который точно так же увеличивается самим GC, и доступ к которому нужно точно так же разграничивать?

    ОтветитьУдалить
  5. А разве для того, чтобы работал GC у каждого объекта не присутвует счетчик ссылок, который точно так же увеличивается самим GC, и доступ к которому нужно точно так же разграничивать?
    Насколько я понимаю, в .NET, это просто указатели. Сборщик мусора строит граф, начиная с корневых объектов(статические объекты, объекты в стеке, и тд), для того, что-бы определить, содержит-ли тот объект указатели на другие объекты используется информация о типах, она там доступна динамически, а не только во время компиляции. Затем, алгоритм GC, с помощью обычного memcpy уплотняет кучу, сдвигая используемые объекты и обновляя указатели на них.

    ОтветитьУдалить
  6. Если я правильно понял, то с копированием объектов этот граф содержащий root будет разрастаться. Скопировали один объект, добавился еще один root на него или не так?

    ОтветитьУдалить
  7. Скажите, вы видели хоть одну программу упершуюся в скорость shared_ptr?

    Ну и какой смысл руссуждать о скорости того, что занимает 0% времени CPU? :-)

    Что же касается скорости, заметьте:

    1 Не все объекты создаются в хипе.
    2 Не под каждый объект создаётся свой блок памяти.
    3 Не на все объекты, под которые создается свой блок памяти, ссылаются используя shared_ptr.

    И, кстати...
    4 сборщик мусора, тоже, не бесплатная вещь.

    ОтветитьУдалить
  8. Скажите, вы видели хоть одну программу упершуюся в скорость shared_ptr?Я видел программы упершиеся в скорость аллокации, а так-же огромное количество ошибок работы с памятью. И я не сомневаюсь, что можно написать такую программу, которая таки упрется в скорость shared_ptr :D

    ОтветитьУдалить
  9. > упершиеся в скорость аллокации

    Вот именно в скорость аллокации, а не скорость shared_ptr.

    Давайте всё-таки различать аллокацию памяти и подсчет ссылок.

    ОтветитьУдалить
  10. Apple, начиная с набора инструментов разработчика для Mac OS X 10.6, настаивает на использование GC в Cocoa-проектах.

    Пруфпик с презенташки одной из сессий WWDC '09

    ОтветитьУдалить
  11. Этот комментарий был удален администратором блога.

    ОтветитьУдалить
  12. Apple, начиная с набора инструментов разработчика для Mac OS X 10.6, настаивает на использование GC в Cocoa-проектах.

    Пруфпик с презенташки одной из сессий WWDC '09

    ОтветитьУдалить
  13. ну судя по статье, CLR реализует стандартную схему с generational gc, но видимо были какие-то особенности реализации, которые просаживали производительность для маленьких объектов - я если найду сслыку на то обсуждение, то кину в комментарий

    ОтветитьУдалить
  14. Если я правильно понял, то с копированием объектов этот граф содержащий root будет разрастаться. Скопировали один объект, добавился еще один root на него или не так?

    ОтветитьУдалить